Skip to content

Commit

Permalink
SAML authentication feature in Velociraptor (Velocidex#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
turekt authored and scudette committed Sep 10, 2019
1 parent 2b89f09 commit 3ac142e
Show file tree
Hide file tree
Showing 17 changed files with 749 additions and 412 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ emacs.desktop
*.debhelper
ab0x.go
output/
.velociraptor_history.json
.velociraptor_history.json
.idea/
xml
node_modules/
package-lock.json
50 changes: 46 additions & 4 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.9.2"

[[constraint]]
name = "github.com/crewjam/saml"
revision = "344d075952c9343809f57f4e465504dd5e3068a4"
4 changes: 3 additions & 1 deletion api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
api_proto "www.velocidex.com/golang/velociraptor/api/proto"
config_proto "www.velocidex.com/golang/velociraptor/config/proto"
"www.velocidex.com/golang/velociraptor/constants"
users "www.velocidex.com/golang/velociraptor/users"
"www.velocidex.com/golang/velociraptor/users"
)

var (
Expand All @@ -43,6 +43,8 @@ func checkUserCredentialsHandler(
if config_obj.GUI.GoogleOauthClientId != "" &&
config_obj.GUI.GoogleOauthClientSecret != "" {
return authenticateOAUTHCookie(config_obj, parent)
} else if SAMLEnabled(config_obj) {
return authenticateSAML(config_obj, parent)
}

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
4 changes: 4 additions & 0 deletions api/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ func PrepareMux(config_obj *config_proto.Config, mux *http.ServeMux) error {
return err
}

if err = MaybeAddSAMLHandlers(config_obj, mux); err != nil {
return err
}

mux.Handle("/api/", checkUserCredentialsHandler(config_obj, h))
mux.Handle("/api/v1/download/", checkUserCredentialsHandler(
config_obj, flowResultDownloadHandler(config_obj)))
Expand Down
107 changes: 107 additions & 0 deletions api/saml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package api

import (
"context"
"encoding/json"
"fmt"
_ "fmt"
"github.com/crewjam/saml/samlsp"
"github.com/sirupsen/logrus"
"net/http"
"net/url"
api_proto "www.velocidex.com/golang/velociraptor/api/proto"
config_proto "www.velocidex.com/golang/velociraptor/config/proto"
"www.velocidex.com/golang/velociraptor/crypto"
"www.velocidex.com/golang/velociraptor/logging"
"www.velocidex.com/golang/velociraptor/users"
)

var samlMiddleware *samlsp.Middleware

func MaybeAddSAMLHandlers(config_obj *config_proto.Config, mux *http.ServeMux) error {
if SAMLEnabled(config_obj) {
key, err := crypto.ParseRsaPrivateKeyFromPemStr([]byte(config_obj.GUI.SamlPrivateKey))
if err != nil {
return err
}

cert, err := crypto.ParseX509CertFromPemStr([]byte(config_obj.GUI.SamlCertificate))
if err != nil {
return err
}

idpMetadataURL, err := url.Parse(config_obj.GUI.SamlIdpMetadataUrl)
if err != nil {
return err
}

rootURL, err := url.Parse(config_obj.GUI.SamlRootUrl)
if err != nil {
return err
}

samlMiddleware, _ = samlsp.New(samlsp.Options{
IDPMetadataURL: idpMetadataURL,
URL: *rootURL,
Key: key,
Certificate: cert,
})
mux.Handle("/saml/", samlMiddleware)
}

return nil
}

func userAttr(config_obj *config_proto.Config) string {
if config_obj.GUI.SamlUserAttribute == "" {
return "name"
}
return config_obj.GUI.SamlUserAttribute
}

func SAMLEnabled(config_obj *config_proto.Config) bool {
return config_obj.GUI.SamlCertificate != "" && config_obj.GUI.SamlPrivateKey != "" &&
config_obj.GUI.SamlIdpMetadataUrl != "" && config_obj.GUI.SamlRootUrl != ""
}

func authenticateSAML(config_obj *config_proto.Config, parent http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if token := samlMiddleware.GetAuthorizationToken(r); token != nil {

username := token.Attributes.Get(userAttr(config_obj))
user_record, err := users.GetUser(config_obj, username)
if err != nil || user_record.Locked || user_record.Name != username {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)

fmt.Fprintf(w, `
<html><body>
Authorization failed. You are not registered on this system as %v.
Contact your system administrator to get an account, then try again.
</body></html>
`, username)

logging.GetLogger(config_obj, &logging.Audit).
WithFields(logrus.Fields{
"user": username,
"remote": r.RemoteAddr,
"method": r.Method,
}).Error("User rejected by GUI")

return
}

user_info := &api_proto.VelociraptorUser{
Name: username,
}

serialized, _ := json.Marshal(user_info)
ctx := context.WithValue(
r.Context(), "USER", string(serialized))
GetLoggingHandler(config_obj)(parent).ServeHTTP(
w, r.WithContext(ctx))
return
}
samlMiddleware.RequireAccountHandler(w, r)
})
}
Loading

0 comments on commit 3ac142e

Please sign in to comment.