Skip to content

Commit

Permalink
Merge pull request #4 from akrejcir/enable-tls
Browse files Browse the repository at this point in the history
Enable TLS and watch changes to certificate and configuration
  • Loading branch information
akrejcir authored Feb 9, 2023
2 parents e39860e + 6eb4ab9 commit b730198
Show file tree
Hide file tree
Showing 105 changed files with 20,794 additions and 582 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ This subprotocol is used for authorization:
The `Service` is not exposed by default, because the `Ingress` configuration
can depend on the cluster where it is running. For example this `Ingress` can be used:

[//]: # (TODO: This ingress currently does not work with OpenShit. Look into why.)

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
Expand All @@ -51,7 +53,7 @@ spec:
service:
name: vm-console-proxy
port:
number: 80
number: 443
path: /
pathType: Prefix
```
7 changes: 7 additions & 0 deletions api/v1alpha1/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package v1alpha1

import (
ocpv1 "github.com/openshift/api/config/v1"
)

// TokenResponse is the response object from /token endpoint.
type TokenResponse struct {
Token string `json:"token"`
}

// TlsSecurityProfile is the TLS configuration for the proxy.
type TlsSecurityProfile = ocpv1.TLSSecurityProfile
9 changes: 1 addition & 8 deletions example-client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,10 @@
status("Connecting");

// Build the websocket URL used to connect
let url;
if (window.location.protocol === "https:") {
url = 'wss';
} else {
url = 'ws';
}
url += '://' + host;
let url = 'wss://' + host;
if(port) {
url += ':' + port;
}

url += '/api/v1alpha1/' + vmNamespace + '/' + vmName + '/vnc'

// Creating a new RFB object will start a new connection
Expand Down
11 changes: 7 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ go 1.19

require (
github.com/emicklei/go-restful/v3 v3.10.1
github.com/fsnotify/fsnotify v1.5.4
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/golang/mock v1.5.0
github.com/gorilla/websocket v1.5.0
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/onsi/ginkgo/v2 v2.6.1
github.com/onsi/gomega v1.24.2
github.com/openshift/api v0.0.0-20220504105152-6f735e7109c8
github.com/openshift/library-go v0.0.0-20220523142556-5bcfed822fc6
golang.org/x/net v0.4.0
k8s.io/api v0.23.5
k8s.io/apimachinery v0.23.5
k8s.io/client-go v12.0.0+incompatible
k8s.io/utils v0.0.0-20211116205334-6203023598ed
kubevirt.io/api v0.0.0-20221013011232-17665f214e18
kubevirt.io/api v0.58.0
kubevirt.io/client-go v0.58.0
)

Expand All @@ -40,13 +43,12 @@ require (
github.com/moby/spdystream v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/openshift/api v0.0.0-20210105115604-44119421ec6b // indirect
github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47 // indirect
github.com/openshift/client-go v0.0.0-20220504114320-6aec01bb0754 // indirect
github.com/openshift/custom-resource-status v1.1.2 // indirect
github.com/pborman/uuid v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
Expand All @@ -57,6 +59,7 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.23.5 // indirect
k8s.io/apiserver v0.23.5 // indirect
k8s.io/klog/v2 v2.40.1 // indirect
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
kubevirt.io/containerized-data-importer-api v1.50.0 // indirect
Expand Down
46 changes: 30 additions & 16 deletions go.sum

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions manifests/config_map.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: vm-console-proxy
data:
tls-profile-v1alpha1.yaml: |
type: Intermediate
intermediate: {}
6 changes: 6 additions & 0 deletions manifests/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ spec:
drop:
- "ALL"
volumeMounts:
- name: "config"
mountPath: "/config"
readOnly: true
- name: "vm-console-proxy-cert"
mountPath: "/tmp/vm-console-proxy-cert"
readOnly: true
Expand All @@ -46,6 +49,9 @@ spec:
readOnly: true
terminationGracePeriodSeconds: 10
volumes:
- name: "config"
configMap:
name: "vm-console-proxy"
- name: "vm-console-proxy-cert"
secret:
secretName: "vm-console-proxy-cert"
Expand Down
1 change: 1 addition & 0 deletions manifests/kustomization.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ kind: Kustomization
namespace: kubevirt

resources:
- config_map.yaml
- service_account.yaml
- role.yaml
- role_binding.yaml
Expand Down
2 changes: 1 addition & 1 deletion manifests/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ metadata:
service.beta.openshift.io/serving-cert-secret-name: vm-console-proxy-cert
spec:
ports:
- port: 80
- port: 443
targetPort: api
selector:
vm-console-proxy.kubevirt.io: vm-console-proxy
31 changes: 0 additions & 31 deletions pkg/console/common.go

This file was deleted.

57 changes: 45 additions & 12 deletions pkg/console/console.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package console

import (
"crypto/tls"
"fmt"
"net/http"
"path/filepath"

"github.com/emicklei/go-restful/v3"
"k8s.io/client-go/metadata"
"kubevirt.io/client-go/kubecli"
"kubevirt.io/client-go/log"

"github.com/kubevirt/vm-console-proxy/pkg/console/dialer"
"github.com/kubevirt/vm-console-proxy/pkg/console/tlsconfig"
"github.com/kubevirt/vm-console-proxy/pkg/token"
)

Expand All @@ -20,6 +24,9 @@ const (

serviceCertPath = "/tmp/vm-console-proxy-cert/tls.crt"
serviceKeyPath = "/tmp/vm-console-proxy-cert/tls.key"

configDir = "/config"
TlsProfileFile = "tls-profile-v1alpha1.yaml"
)

func Run() error {
Expand All @@ -34,21 +41,36 @@ func Run() error {

}

serviceCert, err := LoadCertificates(serviceCertPath, serviceKeyPath)
if err != nil {
return err
}

tokenKey, err := token.CreateHmacKey(serviceCert.PrivateKey)
if err != nil {
return err
}

tlsConfigWatch := tlsconfig.NewWatch(
filepath.Join(configDir, TlsProfileFile),
serviceCertPath,
serviceKeyPath,
)
tlsConfigWatch.Reload()

watchDone := make(chan struct{})
defer close(watchDone)
go func() {
err := tlsConfigWatch.Run(watchDone)
log.Log.Errorf("Error running TLS config watch: %s", err)
}()

tokenKeyCache := token.NewKeyCache()
handlers := &service{
kubevirtClient: cli,
metadataClient: metadataClient,
websocketDialer: dialer.New(),
tokenSigningKey: tokenKey,
getTokenSigningKey: func() ([]byte, error) {
tlsConfig, err := tlsConfigWatch.GetConfig()
if err != nil {
return nil, err
}
tokenKey, err := tokenKeyCache.Get(tlsConfig.Certificates[0].PrivateKey)
if err != nil {
return nil, err
}
return tokenKey, nil
},
}

restful.Add(webService(handlers))
Expand All @@ -60,9 +82,20 @@ func Run() error {

server := &http.Server{
Addr: fmt.Sprintf("%s:%d", defaultAddress, defaultPort),
TLSConfig: &tls.Config{
GetConfigForClient: func(_ *tls.ClientHelloInfo) (*tls.Config, error) {
return tlsConfigWatch.GetConfig()
},
GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
// This function is not called, but it needs to be non-nil, otherwise
// the server tries to load certificate from filenames passed to
// ListenAndServe().
panic("function should not be called")
},
},
}

return server.ListenAndServe()
return server.ListenAndServeTLS("", "")
}

func webService(handlers *service) *restful.WebService {
Expand Down
24 changes: 19 additions & 5 deletions pkg/console/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/kubevirt/vm-console-proxy/api/v1alpha1"
"github.com/kubevirt/vm-console-proxy/pkg/console/dialer"
"github.com/kubevirt/vm-console-proxy/pkg/console/tlsconfig"
"github.com/kubevirt/vm-console-proxy/pkg/token"
)

Expand All @@ -36,8 +37,7 @@ type service struct {
metadataClient metadata.Interface
websocketDialer dialer.Dialer

// TODO: Needs to be refreshed when secret changes
tokenSigningKey []byte
getTokenSigningKey func() ([]byte, error)
}

func (s *service) TokenHandler(request *restful.Request, response *restful.Response) {
Expand Down Expand Up @@ -87,7 +87,14 @@ func (s *service) TokenHandler(request *restful.Request, response *restful.Respo
UID: string(vmiMeta.UID),
}

signedToken, err := token.NewSignedToken(claims, s.tokenSigningKey)
signingKey, err := s.getTokenSigningKey()
if err != nil {
_ = response.WriteErrorString(http.StatusInternalServerError, "error getting token signing key")
log.Log.Errorf("error getting token signing key: %s", err)
return
}

signedToken, err := token.NewSignedToken(claims, signingKey)
if err != nil {
_ = response.WriteError(http.StatusInternalServerError, fmt.Errorf("error signing token: %w", err))
return
Expand All @@ -108,7 +115,14 @@ func (s *service) VncHandler(request *restful.Request, response *restful.Respons
return
}

claims, err := token.ParseToken(authToken, s.tokenSigningKey)
signingKey, err := s.getTokenSigningKey()
if err != nil {
_ = response.WriteErrorString(http.StatusInternalServerError, "error getting token signing key")
log.Log.Errorf("error getting token signing key: %s", err)
return
}

claims, err := token.ParseToken(authToken, signingKey)
if err != nil {
_ = response.WriteErrorString(http.StatusUnauthorized, "request is not authenticated")
return
Expand Down Expand Up @@ -143,7 +157,7 @@ func (s *service) VncHandler(request *restful.Request, response *restful.Respons
InsecureSkipVerify: true,
ClientAuth: tls.RequireAndVerifyClientCert,
GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
return LoadCertificates(virtHandlerCertPath, virtHandlerKeyPath)
return tlsconfig.LoadCertificates(virtHandlerCertPath, virtHandlerKeyPath)
},
}

Expand Down
9 changes: 7 additions & 2 deletions pkg/console/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ var _ = Describe("Service tests", func() {
kubevirtClient: virtClient,
metadataClient: metadataClient,
websocketDialer: testDialer,
tokenSigningKey: []byte("testing-key"),
getTokenSigningKey: func() ([]byte, error) {
return []byte("testing-key"), nil
},
}

request = restful.NewRequest(&http.Request{
Expand Down Expand Up @@ -320,7 +322,10 @@ var _ = Describe("Service tests", func() {
Namespace: testNamespace,
UID: testUid,
}
validToken, err := token.NewSignedToken(claims, testService.tokenSigningKey)
signingKey, err := testService.getTokenSigningKey()
Expect(err).ToNot(HaveOccurred())

validToken, err := token.NewSignedToken(claims, signingKey)
Expect(err).ToNot(HaveOccurred())

request.Request.Header.Set(subprotocolHeader, "base64url.bearer.authorization.k8s.io."+validToken)
Expand Down
Loading

0 comments on commit b730198

Please sign in to comment.