Skip to content
Snippets Groups Projects
Commit 6b6d2bb1 authored by ale's avatar ale
Browse files

Add a SAML/SSO bridge server

Signs SAML assertions for users authenticated via the SSO mechanism.
parent 8e4f07e3
Branches
No related tags found
No related merge requests found
Pipeline #
package main
import (
"context"
"flag"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"git.autistici.org/id/go-sso/saml"
"gopkg.in/yaml.v2"
)
var (
addr = flag.String("addr", ":5004", "address to listen on")
configFile = flag.String("config", "/etc/sso/saml.yml", "`path` of config file")
)
func loadConfig() (*saml.Config, error) {
// Read YAML config.
data, err := ioutil.ReadFile(*configFile)
if err != nil {
return nil, err
}
var config saml.Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
// Set defaults for command-line flags using variables from the environment.
func setFlagDefaultsFromEnv() {
flag.VisitAll(func(f *flag.Flag) {
envVar := "SAML_" + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
if value := os.Getenv(envVar); value != "" {
f.DefValue = value
f.Value.Set(value)
}
})
}
func main() {
setFlagDefaultsFromEnv()
flag.Parse()
config, err := loadConfig()
if err != nil {
log.Fatal(err)
}
s, err := saml.NewSAMLIDP(config)
if err != nil {
log.Fatal(err)
}
srv := &http.Server{
Addr: *addr,
Handler: s,
}
sigCh := make(chan os.Signal, 1)
go func() {
<-sigCh
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
_ = srv.Close()
}()
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
File added
...@@ -16,3 +16,9 @@ Architecture: any ...@@ -16,3 +16,9 @@ Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends} Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Single-Sign-On HTTP proxy. Description: Single-Sign-On HTTP proxy.
Single-Sign-On HTTP proxy. Single-Sign-On HTTP proxy.
Package: saml-server
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: SAML Single-Sign-On bridge
SAML Single-Sign-On bridge.
ADDR=:5004
usr/bin/saml-server
#!/bin/sh
set -e
case "$1" in
configure)
addgroup --system --quiet sso-proxy
adduser --system --no-create-home --home /run/sso-proxy \
--disabled-password --disabled-login \
--quiet --ingroup sso-proxy sso-proxy
;;
esac
#DEBHELPER#
exit 0
[Unit]
Description=SAML SSO Bridge
[Service]
User=saml-server
Group=saml-server
EnvironmentFile=-/etc/default/saml-server
ExecStart=/usr/bin/saml-server --addr $ADDR
Restart=always
[Install]
WantedBy=multi-user.target
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
...@@ -22,13 +23,6 @@ import ( ...@@ -22,13 +23,6 @@ import (
"git.autistici.org/id/go-sso/httpsso" "git.autistici.org/id/go-sso/httpsso"
) )
type ServiceProviderConfig struct {
}
func (p *ServiceProviderConfig) toEntity() *saml.EntityDescriptor {
return nil
}
type Config struct { type Config struct {
BaseURL string `yaml:"base_url"` BaseURL string `yaml:"base_url"`
...@@ -46,7 +40,8 @@ type Config struct { ...@@ -46,7 +40,8 @@ type Config struct {
SSODomain string `yaml:"sso_domain"` SSODomain string `yaml:"sso_domain"`
// Service provider config. // Service provider config.
ServiceProviders map[string]*ServiceProviderConfig `yaml:"service_providers"` ServiceProviders []string `yaml:"service_providers"`
parsedServiceProviders map[string]*saml.EntityDescriptor
} }
// Sanity checks for the configuration. // Sanity checks for the configuration.
...@@ -74,12 +69,28 @@ func (c *Config) check() error { ...@@ -74,12 +69,28 @@ func (c *Config) check() error {
return nil return nil
} }
func (c *Config) loadServiceProviders() error {
c.parsedServiceProviders = make(map[string]*saml.EntityDescriptor)
for _, path := range c.ServiceProviders {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var ent saml.EntityDescriptor
if err := xml.Unmarshal(data, &ent); err != nil {
return err
}
c.parsedServiceProviders[ent.EntityID] = &ent
}
return nil
}
func (c *Config) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) { func (c *Config) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) {
srv, ok := c.ServiceProviders[serviceProviderID] srv, ok := c.parsedServiceProviders[serviceProviderID]
if !ok { if !ok {
return nil, os.ErrNotExist return nil, os.ErrNotExist
} }
return srv.toEntity(), nil return srv, nil
} }
// Read users from a YAML-encoded file, in a format surprisingly // Read users from a YAML-encoded file, in a format surprisingly
...@@ -142,6 +153,9 @@ func NewSAMLIDP(config *Config) (http.Handler, error) { ...@@ -142,6 +153,9 @@ func NewSAMLIDP(config *Config) (http.Handler, error) {
if err := config.check(); err != nil { if err := config.check(); err != nil {
return nil, err return nil, err
} }
if err := config.loadServiceProviders(); err != nil {
return nil, err
}
cert, err := tls.LoadX509KeyPair(config.CertificateFile, config.PrivateKeyFile) cert, err := tls.LoadX509KeyPair(config.CertificateFile, config.PrivateKeyFile)
if err != nil { if err != nil {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment