From c4f6ffce92d05e663151dcc6296cf2e710414476 Mon Sep 17 00:00:00 2001 From: Max Erenberg Date: Wed, 29 Dec 2021 00:36:47 -0500 Subject: [PATCH] first commit --- .gitignore | 3 + README.md | 21 ++++ config.json | 14 +++ go.mod | 8 ++ go.sum | 64 ++++++++++++ main.go | 161 +++++++++++++++++++++++++++++++ service_provider_provider.go | 118 ++++++++++++++++++++++ session_provider.go | 75 ++++++++++++++ systemd/saml-passthrough.service | 16 +++ 9 files changed, 480 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 service_provider_provider.go create mode 100644 session_provider.go create mode 100644 systemd/saml-passthrough.service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..791ba37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.key +*.crt +/saml-passthrough diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1b2306 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# saml-passthrough +This program is intended to run behind the Apache mod_auth_mellon module. +It receives ADFS user information from Apache over FastCGI, then +passes it back to Keycloak (which is acting as a SAML SP). + +## Create a new keypair +```sh +openssl req -newkey rsa:2048 -nodes -keyout idp.key -x509 -out idp.crt -days 3680 -subj '/CN=SAML Passthrough/O=Computer Science Club' +``` +Make sure to renew the cert in ten years. + +## Apache config +Add the following snippet to /etc/apache2/sites-real/csc (and make sure mod_proxy_fcgi is enabled): +``` + + AuthType Mellon + MellonEnable auth + Require valid-user + SetHandler "proxy:unix:/run/saml-passthrough/server.sock|fcgi://localhost" + +``` diff --git a/config.json b/config.json new file mode 100644 index 0000000..f5eb7e6 --- /dev/null +++ b/config.json @@ -0,0 +1,14 @@ +{ + "idp_key_path": "./idp.key", + "idp_cert_path": "./idp.crt", + "username_key": "MELLON_samaccountname", + "email_key": "MELLON_emailaddress", + "given_name_key": "MELLON_givenname", + "surname_key": "MELLON_http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", + "groups_key": "ADFS_GROUP", + "base_url": "https://csclub.uwaterloo.ca/keycloak/saml", + "sp_metadata_paths": [ + "https://keycloak.csclub.uwaterloo.ca/auth/realms/csc/broker/adfs/endpoint/descriptor" + ], + "socket_path": "/run/saml-passthrough/server.sock" +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1864cef --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module git.csclub.uwaterloo.ca/merenber/saml-passthrough + +go 1.15 + +require ( + github.com/crewjam/saml v0.4.6 + github.com/mattermost/xml-roundtrip-validator v0.1.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..09bf7cd --- /dev/null +++ b/go.sum @@ -0,0 +1,64 @@ +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/crewjam/saml v0.4.6 h1:XCUFPkQSJLvzyl4cW9OvpWUbRf0gE7VUpU8ZnilbeM4= +github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= +github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= +github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= +github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9fc0c4e --- /dev/null +++ b/main.go @@ -0,0 +1,161 @@ +package main + +import ( + "crypto" + "crypto/x509" + "encoding/json" + "encoding/pem" + "flag" + "io/ioutil" + "net" + "net/http" + "net/http/fcgi" + "net/url" + "os" + + "github.com/crewjam/saml" + "github.com/crewjam/saml/logger" +) + +// adapted from https://github.com/crewjam/saml/blob/main/example/idp/idp.go +func parsePEM(filename string) *pem.Block { + raw, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + block, _ := pem.Decode(raw) + if block == nil { + panic("Could not decode PEM from " + filename) + } + return block +} + +type Config struct { + // The filesystem path to the private key to use for the IDP. + // The key should be in PKCS8 PEM format. + IDPKeyPath string `json:"idp_key_path"` + // The filesystem path to the certificate to use for the IDP. + // The certificate should be in PEM format. + IDPCertPath string `json:"idp_cert_path"` + // The name of the FCGI variable which has the user's username. + UsernameKey string `json:"username_key"` + // The name of the FCGI variable which has the user's email. + EmailKey string `json:"email_key"` + // The name of the FCGI variable which has the user's first name. + GivenNameKey string `json:"given_name_key"` + // The name of the FCGI variable which has the user's last name. + SurnameKey string `json:"surname_key"` + // The name of the FCGI variable which has the user's groups. + // The groups should be concatenated together into one string + // separated by semicolons, e.g. "group1;group2". + GroupsKey string `json:"groups_key"` + // The base URL for the IDP. The following endpoints will be + // derived from it: + // Metadata: baseURL + "/metadata" + // SSO login: baseURL + "/sso" + BaseURL string `json:"base_url"` + // An array of filepaths or HTTP URLs of the service providers' + // metadata XML files. + SPMetadataPaths []string `json:"sp_metadata_paths"` + // The filesystem path of the Unix domain socket on which this + // program will listen for incoming FCGI connections. + SocketPath string `json:"socket_path"` +} + +func newIDP(cfg *Config) *saml.IdentityProvider { + key := func() crypto.PrivateKey { + block := parsePEM(cfg.IDPKeyPath) + k, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + panic(err) + } + return k + }() + cert := func() *x509.Certificate { + block := parsePEM(cfg.IDPCertPath) + c, err := x509.ParseCertificate(block.Bytes) + if err != nil { + panic(err) + } + return c + }() + sessionProvider := &BasicSessionProvider{ + UsernameKey: cfg.UsernameKey, + EmailKey: cfg.EmailKey, + GivenNameKey: cfg.GivenNameKey, + SurnameKey: cfg.SurnameKey, + GroupsKey: cfg.GroupsKey, + } + serviceProviderProvider := NewServiceProviderProvider(cfg.SPMetadataPaths) + metadataURL, err := url.Parse(cfg.BaseURL + "/metadata") + if err != nil { + panic(err) + } + ssoURL, err := url.Parse(cfg.BaseURL + "/sso") + if err != nil { + panic(err) + } + return &saml.IdentityProvider{ + Key: key, + Certificate: cert, + Logger: logger.DefaultLogger, + MetadataURL: *metadataURL, + SSOURL: *ssoURL, + SessionProvider: sessionProvider, + ServiceProviderProvider: serviceProviderProvider, + } +} + +func newHTTPHandler(idp *saml.IdentityProvider) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc(idp.MetadataURL.Path, idp.ServeMetadata) + mux.HandleFunc(idp.SSOURL.Path, idp.ServeSSO) + return mux +} + +func newListener(cfg *Config) net.Listener { + var err error + sockPath := cfg.SocketPath + if _, err = os.Stat(sockPath); err == nil { + err = os.Remove(sockPath) + if err != nil { + panic(err) + } + } + ln, err := net.Listen("unix", sockPath) + if err != nil { + panic(err) + } + os.Chmod(sockPath, 0777) + return ln +} + +func main() { + // Allow the key path to be overriden via a CLI flag so that we + // can use systemd's LoadCredential feature + idpKeyPathPtr := flag.String("k", "", "IDP key path") + configPathPtr := flag.String("c", "./config.json", "config file path") + flag.Parse() + + cfgBytes, err := ioutil.ReadFile(*configPathPtr) + if err != nil { + panic(err) + } + cfg := Config{} + err = json.Unmarshal(cfgBytes, &cfg) + if err != nil { + panic(err) + } + if *idpKeyPathPtr != "" { + cfg.IDPKeyPath = *idpKeyPathPtr + } + + idp := newIDP(&cfg) + handler := newHTTPHandler(idp) + listener := newListener(&cfg) + + err = fcgi.Serve(listener, handler) + if err != nil { + panic(err) + } +} diff --git a/service_provider_provider.go b/service_provider_provider.go new file mode 100644 index 0000000..c44be75 --- /dev/null +++ b/service_provider_provider.go @@ -0,0 +1,118 @@ +// adapted from https://github.com/crewjam/saml/blob/main/samlidp/service.go +package main + +import ( + "bytes" + "encoding/xml" + "errors" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + + "github.com/crewjam/saml" + xrv "github.com/mattermost/xml-roundtrip-validator" +) + +// copied from https://github.com/crewjam/saml/blob/main/samlidp/util.go +func getSPMetadata(r io.Reader) (spMetadata *saml.EntityDescriptor, err error) { + var data []byte + if data, err = ioutil.ReadAll(r); err != nil { + return nil, err + } + + spMetadata = &saml.EntityDescriptor{} + if err := xrv.Validate(bytes.NewBuffer(data)); err != nil { + return nil, err + } + + if err := xml.Unmarshal(data, &spMetadata); err != nil { + if err.Error() == "expected element type but have " { + entities := &saml.EntitiesDescriptor{} + if err := xml.Unmarshal(data, &entities); err != nil { + return nil, err + } + + for _, e := range entities.EntityDescriptors { + if len(e.SPSSODescriptors) > 0 { + return &e, nil + } + } + + // there were no SPSSODescriptors in the response + return nil, errors.New("metadata contained no service provider metadata") + } + + return nil, err + } + + return spMetadata, nil +} + +type BasicServiceProviderProvider struct { + serviceProviders map[string]*saml.EntityDescriptor +} + +func modifyACSBindings(entityDescriptor *saml.EntityDescriptor) { + // The SAML library only allows the HTTP-POST Binding (from the IdP + // to the SP), so we need to modify the AssertionConsumerService + // endpoints which use HTTP-Redirect to use HTTP-POST. + for i := 0; i < len(entityDescriptor.SPSSODescriptors); i++ { + spSSODescriptor := &entityDescriptor.SPSSODescriptors[i] + for j := 0; j < len(spSSODescriptor.AssertionConsumerServices); j++ { + acs := &spSSODescriptor.AssertionConsumerServices[j] + if acs.Binding == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" { + log.Printf("Replacing Binding for %s from HTTP-Redirect to HTTP-POST", acs.Location) + acs.Binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + } + } + } +} + +func NewServiceProviderProvider(spMetadataPaths []string) saml.ServiceProviderProvider { + spp := &BasicServiceProviderProvider{ + serviceProviders: make(map[string]*saml.EntityDescriptor), + } + client := http.Client{} + loadSPMetadata := func(filename string) (*saml.EntityDescriptor, error) { + var r io.ReadCloser + var err error + if strings.HasPrefix(filename, "http://") || strings.HasPrefix(filename, "https://") { + resp, err := client.Get(filename) + if err != nil { + return nil, err + } + r = resp.Body + } else { + r, err = os.Open(filename) + if err != nil { + return nil, err + } + } + defer r.Close() + return getSPMetadata(r) + } + for _, filename := range spMetadataPaths { + metadata, err := loadSPMetadata(filename) + if err != nil { + panic(err) + } + modifyACSBindings(metadata) + spp.serviceProviders[metadata.EntityID] = metadata + } + return spp +} + +// GetServiceProvider returns the Service Provider metadata for the +// service provider ID, which is typically the service provider's +// metadata URL. If an appropriate service provider cannot be found then +// the returned error must be os.ErrNotExist. +func (s *BasicServiceProviderProvider) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) { + rv, ok := s.serviceProviders[serviceProviderID] + if !ok { + return nil, os.ErrNotExist + } + return rv, nil +} diff --git a/session_provider.go b/session_provider.go new file mode 100644 index 0000000..5b58edc --- /dev/null +++ b/session_provider.go @@ -0,0 +1,75 @@ +// adapted from https://github.com/crewjam/saml/blob/main/samlidp/session.go +package main + +import ( + "encoding/base64" + "encoding/hex" + "log" + "net/http" + "net/http/fcgi" + "strings" + "time" + + "github.com/crewjam/saml" +) + +var sessionMaxAge = time.Hour + +func randomBytes(n int) []byte { + rv := make([]byte, n) + if _, err := saml.RandReader.Read(rv); err != nil { + panic(err) + } + return rv +} + +type User struct { + Username string + Email string + GivenName string + Surname string + Groups []string +} + +type BasicSessionProvider struct { + UsernameKey string + EmailKey string + GivenNameKey string + SurnameKey string + GroupsKey string +} + +func (s *BasicSessionProvider) getUser(r *http.Request) *User { + env := fcgi.ProcessEnv(r) + user := &User{} + user.Username, _ = env[s.UsernameKey] + user.Email, _ = env[s.EmailKey] + user.GivenName, _ = env[s.GivenNameKey] + user.Surname, _ = env[s.SurnameKey] + groupsStr, _ := env[s.GroupsKey] + user.Groups = strings.Split(groupsStr, ";") + return user +} + +func (s *BasicSessionProvider) GetSession(w http.ResponseWriter, r *http.Request, req *saml.IdpAuthnRequest) *saml.Session { + user := s.getUser(r) + if user.Username == "" { + log.Println("Could not obtain username from FCGI") + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return nil + } + + session := &saml.Session{ + ID: base64.StdEncoding.EncodeToString(randomBytes(32)), + NameID: user.Username, + CreateTime: saml.TimeNow(), + ExpireTime: saml.TimeNow().Add(sessionMaxAge), + Index: hex.EncodeToString(randomBytes(32)), + UserName: user.Username, + Groups: user.Groups, + UserEmail: user.Email, + UserSurname: user.Surname, + UserGivenName: user.GivenName, + } + return session +} diff --git a/systemd/saml-passthrough.service b/systemd/saml-passthrough.service new file mode 100644 index 0000000..d4ad7fd --- /dev/null +++ b/systemd/saml-passthrough.service @@ -0,0 +1,16 @@ +[Unit] +Description=SAML passthrough for Keycloak +Documentation=https://git.csclub.uwaterloo.ca/merenber/saml-passthrough +Requires=apache2.service +After=apache2.service + +[Service] +Type=exec +WorkingDirectory=/srv/saml-passthrough +RuntimeDirectory=saml-passthrough +DynamicUser=yes +LoadCredential=idp.key:/srv/saml-passthrough/idp.key +ExecStart=/srv/saml-passthrough/saml-passthrough -k "${CREDENTIALS_DIRECTORY}/idp.key" + +[Install] +WantedBy=multi-user.target