Skip to content

Commit

Permalink
Allow clients to present client certificates for mTLS authentication (V…
Browse files Browse the repository at this point in the history
…elocidex#3133)

This PR allows clients to include cilent side certificates and adds a
server option to require those. This additonal authentication
requirement between clients and server ensure that only clients with a
valid issued certificate can connect to the Velociraptor server.

Also added docs for ECS and a notebook to help upload to Elastic.
  • Loading branch information
scudette authored Nov 28, 2023
1 parent 7e92e71 commit f72dda5
Show file tree
Hide file tree
Showing 14 changed files with 8,046 additions and 203 deletions.
96 changes: 71 additions & 25 deletions api/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (self *Builder) withAutoCertFrontendSelfSignedGUI(
if config_obj.Services.GuiServer && config_obj.GUI != nil {
mux := http.NewServeMux()

router, err := PrepareGUIMux(ctx, config_obj, mux)
router, err := PrepareGUIMux(ctx, config_obj, server_obj, mux)
if err != nil {
return err
}
Expand Down Expand Up @@ -177,7 +177,7 @@ func (self *Builder) WithAutocertGUI(
}
}

router, err := PrepareGUIMux(ctx, self.config_obj, mux)
router, err := PrepareGUIMux(ctx, self.config_obj, self.server_obj, mux)
if err != nil {
return err
}
Expand Down Expand Up @@ -207,19 +207,37 @@ func startSharedSelfSignedFrontend(
}
}

router, err := PrepareGUIMux(ctx, config_obj, mux)
router, err := PrepareGUIMux(ctx, config_obj, server_obj, mux)
if err != nil {
return err
}

// Combine both frontend and GUI on HTTP server.
if config_obj.GUI.UsePlainHttp && config_obj.Frontend.UsePlainHttp {
server_obj.Info("Frontend and GUI both share port with plain HTTP %v",
config_obj.Frontend.BindPort)

return StartFrontendPlainHttp(
ctx, wg, config_obj, server_obj, mux)
}

return StartFrontendHttps(ctx, wg,
config_obj, server_obj, router)
server_obj.Info("Frontend and GUI both share port %v",
config_obj.Frontend.BindPort)

auther, err := authenticators.NewAuthenticator(config_obj)
if err != nil {
return err
}

if config_obj.Frontend.RequireClientCertificates != auther.RequireClientCerts() {
return errors.New(
"When using configurations that place the Frontend and GUI on the same port and requiring mTLS client certificates, then the GUI must also use the client certificate authenticator. Either split the frotnend and GUI on different ports or use the ClientCertificate authenticator.")
}

if config_obj.Frontend.RequireClientCertificates {
server_obj.Info("Frontend and GUI will both require mTLS client side certificates!")
}
return StartFrontendHttps(ctx, wg, config_obj, server_obj, router)
}

// Start the Frontend and GUI on different ports using different
Expand All @@ -238,7 +256,7 @@ func startSelfSignedFrontend(
if config_obj.Services.GuiServer {
mux := http.NewServeMux()

router, err := PrepareGUIMux(ctx, config_obj, mux)
router, err := PrepareGUIMux(ctx, config_obj, server_obj, mux)
if err != nil {
return err
}
Expand Down Expand Up @@ -314,6 +332,14 @@ func StartFrontendHttps(
return err
}

if config_obj.Frontend.RequireClientCertificates {
err = addClientCerts(config_obj, tls_config)
if err != nil {
return err
}
server_obj.Info("Frontend will require mTLS client side certificates!")
}

listenAddr := fmt.Sprintf(
"%s:%d",
config_obj.Frontend.BindAddress,
Expand Down Expand Up @@ -475,15 +501,26 @@ func StartFrontendWithAutocert(
return err
}

err = maybeAddClientCerts(config_obj, tls_config)
auther, err := authenticators.NewAuthenticator(config_obj)
if err != nil {
return err
}

// The frontend can not work with client certs required, so if we are in
if tls_config.ClientAuth == tls.RequireAndVerifyClientCert {
// The frontend can not work with client certs required, so if we
// are in autocert mode we need either both frontend and gui to be
// configured with client cert or neither.
if config_obj.Frontend.RequireClientCertificates != auther.RequireClientCerts() {
return errors.New(
"The Frontend can not work with client Certificate authenticators. Make sure the GUI is listening on a different port to the Frontend!")
"When using configurations that place the Frontend and GUI on the same port and requiring mTLS client certificates, then the GUI must also use the client certificate authenticator. Either split the Frotnend and GUI on different ports or use the ClientCertificate authenticator.")
}

if auther.RequireClientCerts() {
err = addClientCerts(config_obj, tls_config)
if err != nil {
return err
}

server_obj.Info("Frontend and GUI will require mTLS client side certificates!")
}

// Autocert selects its own certificates by itself
Expand Down Expand Up @@ -641,11 +678,18 @@ func StartSelfSignedGUI(

// If we are using an authenticator that requires client side
// certs, add the required TLS config here.
err = maybeAddClientCerts(config_obj, tls_config)
auther, err := authenticators.NewAuthenticator(config_obj)
if err != nil {
return err
}

if auther.RequireClientCerts() {
err = addClientCerts(config_obj, tls_config)
if err != nil {
return err
}
}

listenAddr := fmt.Sprintf("%s:%d",
config_obj.GUI.BindAddress,
config_obj.GUI.BindPort)
Expand Down Expand Up @@ -703,20 +747,21 @@ func get_hostname(fe_hostname, bind_addr string) string {
return bind_addr
}

func maybeAddClientCerts(config_obj *config_proto.Config, in *tls.Config) error {
auther, err := authenticators.NewAuthenticator(config_obj)
if err != nil {
return err
}

if !auther.RequireClientCerts() {
return nil
}

func addClientCerts(config_obj *config_proto.Config, in *tls.Config) error {
// Require the browser to use client certificates
client_ca := x509.NewCertPool()
if config_obj.Client != nil {
client_ca.AppendCertsFromPEM([]byte(config_obj.Client.CaCertificate))

// Also trust any of our trusted root CAs.
if config_obj.Client.Crypto != nil &&
config_obj.Client.Crypto.RootCerts != "" {
if !client_ca.AppendCertsFromPEM(
[]byte(config_obj.Client.Crypto.RootCerts)) {
return errors.New(
"Unable to parse Crypto.root_certs in the config file.")
}
}
}

in.ClientAuth = tls.RequireAndVerifyClientCert
Expand All @@ -735,10 +780,11 @@ func getTLSConfig(config_obj *config_proto.Config, in *tls.Config) error {
}

expected_clients := int64(20000)
if config_obj.Frontend != nil &&
config_obj.Frontend.Resources != nil &&
config_obj.Frontend.Resources.ExpectedClients > 0 {
expected_clients = config_obj.Frontend.Resources.ExpectedClients
if config_obj.Frontend != nil {
if config_obj.Frontend.Resources != nil &&
config_obj.Frontend.Resources.ExpectedClients > 0 {
expected_clients = config_obj.Frontend.Resources.ExpectedClients
}
}

in.Certificates = certs
Expand Down
8 changes: 7 additions & 1 deletion api/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
crypto_utils "www.velocidex.com/golang/velociraptor/crypto/utils"
"www.velocidex.com/golang/velociraptor/grpc_client"
"www.velocidex.com/golang/velociraptor/logging"
"www.velocidex.com/golang/velociraptor/server"
)

// A Mux for the reverse proxy feature.
Expand Down Expand Up @@ -102,7 +103,9 @@ func AddProxyMux(config_obj *config_proto.Config, mux *http.ServeMux) error {
// Prepares a mux for the GUI by adding handlers required by the GUI.
func PrepareGUIMux(
ctx context.Context,
config_obj *config_proto.Config, mux *http.ServeMux) (http.Handler, error) {
config_obj *config_proto.Config,
server_obj *server.Server,
mux *http.ServeMux) (http.Handler, error) {
if config_obj.GUI == nil {
return nil, errors.New("GUI not configured")
}
Expand All @@ -121,6 +124,9 @@ func PrepareGUIMux(
if err != nil {
return nil, err
}
if config_obj.GUI != nil && config_obj.GUI.Authenticator != nil {
server_obj.Info("GUI will use the %v authenticator", config_obj.GUI.Authenticator.Type)
}

// Add the authenticator specific handlers.
err = auther.AddHandlers(mux)
Expand Down
25 changes: 25 additions & 0 deletions artifacts/definitions/Elastic/EventLogs/Sysmon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,28 @@ sources:
FROM parse_evtx(filename=OSPath)
})
}, column="ECS")

notebook:
- type: vql_suggestion
name: "Upload to Elastic"
template: |
/*
* Modify the Elastic parameters to upload this dataset.
* You might need to add authentication to Elastic.
*/
LET ElasicAddress = "http://localhost:9200"

// Uncomment this when you are ready to upload the data
LET X = SELECT *
FROM elastic_upload(
addresses=ElasicAddress,
index="winlogbeat-velo",
action="create",
query={
SELECT timestamp(epoch=now()) AS `@timestamp`,
ClientId,
client_info(client_id=ClientId).Hostname AS Hostname,
*
FROM source(artifact="Elastic.EventLogs.Sysmon")
LIMIT 10
})
Loading

0 comments on commit f72dda5

Please sign in to comment.