Skip to content

Commit a132735

Browse files
committed
Use github.com/rancher/saml and also change option handling
1 parent f4cad4b commit a132735

File tree

7 files changed

+194
-110
lines changed

7 files changed

+194
-110
lines changed

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ module github.com/andrewheberle/go-http-auth-server
22

33
go 1.21.4
44

5+
replace github.com/crewjam/saml v0.4.14 => github.com/rancher/saml v0.4.14-rancher3
6+
57
require (
68
github.com/cloudflare/certinel v0.4.1
79
github.com/crewjam/saml v0.4.14
@@ -15,7 +17,7 @@ require (
1517
)
1618

1719
require (
18-
github.com/beevik/etree v1.1.0 // indirect
20+
github.com/beevik/etree v1.2.0 // indirect
1921
github.com/crewjam/httperr v0.2.0 // indirect
2022
github.com/fsnotify/fsnotify v1.7.0 // indirect
2123
github.com/hashicorp/hcl v1.0.0 // indirect

go.sum

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
21
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
2+
github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw=
3+
github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
34
github.com/cloudflare/certinel v0.4.1 h1:b0nGqKxEjCe6aS3SoZf0HwjkzfCCAqGzZj8iB9ZJGW0=
45
github.com/cloudflare/certinel v0.4.1/go.mod h1:hcx0SA3fmeMzo6egeOzN/29/xfA4+bhZttHvR20a4YA=
56
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
67
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
78
github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo=
89
github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
9-
github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
10-
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
1110
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1211
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1312
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -53,6 +52,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
5352
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5453
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
5554
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55+
github.com/rancher/saml v0.4.14-rancher3 h1:2NN6cPqm9FJeiT25x8+gLHWGdulsEak33cHRkGaJ5v0=
56+
github.com/rancher/saml v0.4.14-rancher3/go.mod h1:S4+611dxnKt8z/ulbvaJzcgSHsuhjVc1QHNTcr1R7Fw=
5657
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
5758
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
5859
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=

internal/cmd/root.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func init() {
4444
rootCmd.Flags().String("sp-cert", "", "Service Provider Certificate")
4545
rootCmd.Flags().String("sp-key", "", "Service Provider Key")
4646
rootCmd.Flags().String("sp-url", "http://localhost:9091", "Service Provider URL")
47-
rootCmd.Flags().StringToString("sp-claim-mapping", map[string]string{"remote-user": "urn:oasis:names:tc:SAML:attribute:subject-id", "remote-email": "mail", "remote-name": "displayName", "remote-groups": "role"}, "Mapping of claims to headers")
47+
rootCmd.Flags().StringToString("sp-claim-mapping", sp.DefaultClaimMapping, "Mapping of claims to headers")
4848
rootCmd.Flags().String("idp-metadata", "", "IdP Metadata URL")
4949
rootCmd.Flags().String("idp-issuer", "", "IdP Issuer/Entity ID")
5050
rootCmd.Flags().String("idp-sso-endpoint", "", "IdP SSO/login Endpoint")
@@ -94,32 +94,32 @@ func runRootCmd() error {
9494
return fmt.Errorf("problem with SP URL: %w", err)
9595
}
9696

97-
// set up metadata
98-
metadata, err := func() (interface{}, error) {
99-
if m := viper.GetString("idp-metadata"); m != "" {
100-
// from url
101-
return url.Parse(m)
102-
}
97+
// set up service provider options
98+
opts := []sp.ServiceProviderOption{
99+
sp.WithClaimMapping(viper.GetStringMapString("sp-claim-mapping")),
100+
}
103101

104-
// error if the options for custom metadata are not set
105-
if viper.GetString("idp-issuer") == "" {
106-
return nil, fmt.Errorf("no metadata url or IdP information provided")
102+
// handle metadata
103+
if m := viper.GetString("idp-metadata"); m != "" {
104+
metadata, err := url.Parse(m)
105+
if err != nil {
106+
return fmt.Errorf("problem parsing IdP metadata url: %w", err)
107107
}
108108

109-
// or custom made
110-
return sp.ServiceProviderMetadata{
109+
opts = append(opts, sp.WithMetadataURL(metadata))
110+
} else {
111+
metadata := sp.ServiceProviderMetadata{
111112
Issuer: viper.GetString("idp-issuer"),
112113
Endpoint: viper.GetString("idp-sso-endpoint"),
113114
NameId: "persistent",
114115
Certificate: viper.GetString("idp-certificate"),
115-
}, nil
116-
}()
117-
if err != nil {
118-
return fmt.Errorf("problem with settign up IdP metadata: %w", err)
116+
}
117+
118+
opts = append(opts, sp.WithCustomMetadata(metadata))
119119
}
120120

121121
// set up auth provider
122-
provider, err := sp.NewServiceProvider(viper.GetString("sp-cert"), viper.GetString("sp-key"), metadata, root, viper.GetStringMapString("sp-claim-mapping"))
122+
provider, err := sp.NewServiceProvider(viper.GetString("sp-cert"), viper.GetString("sp-key"), root, opts...)
123123
if err != nil {
124124
return fmt.Errorf("problem setting up SP: %w", err)
125125
}
@@ -211,8 +211,20 @@ func runRootCmd() error {
211211
default:
212212
time.Sleep(time.Hour * 24)
213213

214+
// parse url
215+
metadata, _ := url.Parse(viper.GetString("idp-metadata"))
216+
if err != nil {
217+
return fmt.Errorf("problem parsing IdP metadata url: %w", err)
218+
}
219+
220+
// set up service provider options
221+
opts := []sp.ServiceProviderOption{
222+
sp.WithClaimMapping(viper.GetStringMapString("sp-claim-mapping")),
223+
sp.WithMetadataURL(metadata),
224+
}
225+
214226
// set up provider
215-
provider, err := sp.NewServiceProvider(viper.GetString("sp-cert"), viper.GetString("sp-key"), metadata, root, viper.GetStringMapString("sp-claim-mapping"))
227+
provider, err := sp.NewServiceProvider(viper.GetString("sp-cert"), viper.GetString("sp-key"), root, opts...)
216228
if err != nil {
217229
// not a fatal error
218230
slog.Error("saml service provider reload", "error", err)

pkg/sp/codec.go

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import (
44
"errors"
55
"fmt"
66
"log/slog"
7-
"sync"
87

98
"github.com/crewjam/saml/samlsp"
109
"github.com/golang-jwt/jwt/v4"
1110
)
1211

1312
type JWTSessionCodec struct {
1413
samlsp.JWTSessionCodec
15-
store *AttributeStore
14+
store AttributeStore
1615
}
1716

1817
func (c JWTSessionCodec) Encode(s samlsp.Session) (string, error) {
@@ -64,52 +63,8 @@ func (c JWTSessionCodec) Decode(signed string) (samlsp.Session, error) {
6463
return claims, nil
6564
}
6665

67-
type AttributeStore struct {
68-
store map[string]samlsp.Attributes
69-
mu sync.RWMutex
70-
}
71-
72-
func NewAttributeStore() *AttributeStore {
73-
return &AttributeStore{
74-
store: make(map[string]samlsp.Attributes),
75-
}
76-
}
77-
78-
func (s *AttributeStore) Get(id string) (samlsp.Attributes, error) {
79-
s.mu.RLock()
80-
defer s.mu.RUnlock()
81-
82-
if attrs, found := s.store[id]; found {
83-
slog.Debug("getting attributes from store", "id", id, "attrs", attrs)
84-
85-
return attrs, nil
86-
}
87-
88-
return nil, fmt.Errorf("not found")
89-
}
90-
91-
func (s *AttributeStore) Set(id string, attrs samlsp.Attributes) {
92-
s.mu.Lock()
93-
defer s.mu.Unlock()
94-
95-
if s.store == nil {
96-
s.store = make(map[string]samlsp.Attributes)
97-
}
98-
99-
slog.Debug("setting attributes in store", "id", id, "attrs", attrs)
100-
101-
s.store[id] = attrs
102-
}
103-
104-
func (s *AttributeStore) Delete(id string) {
105-
s.mu.Lock()
106-
defer s.mu.Unlock()
107-
108-
if s.store == nil {
109-
return
110-
}
111-
112-
slog.Debug("deleting attributes in store", "id", id)
113-
114-
delete(s.store, id)
66+
type AttributeStore interface {
67+
Get(id string) (samlsp.Attributes, error)
68+
Set(id string, attrs samlsp.Attributes)
69+
Delete(id string)
11570
}

pkg/sp/memorystore.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package sp
2+
3+
import (
4+
"fmt"
5+
"log/slog"
6+
"sync"
7+
8+
"github.com/crewjam/saml/samlsp"
9+
)
10+
11+
type MemoryAttributeStore struct {
12+
store map[string]samlsp.Attributes
13+
mu sync.RWMutex
14+
}
15+
16+
func NewMemoryAttributeStore() *MemoryAttributeStore {
17+
return &MemoryAttributeStore{
18+
store: make(map[string]samlsp.Attributes),
19+
}
20+
}
21+
22+
func (s *MemoryAttributeStore) Get(id string) (samlsp.Attributes, error) {
23+
s.mu.RLock()
24+
defer s.mu.RUnlock()
25+
26+
if attrs, found := s.store[id]; found {
27+
slog.Debug("getting attributes from store", "id", id, "attrs", attrs)
28+
29+
return attrs, nil
30+
}
31+
32+
return nil, fmt.Errorf("not found")
33+
}
34+
35+
func (s *MemoryAttributeStore) Set(id string, attrs samlsp.Attributes) {
36+
s.mu.Lock()
37+
defer s.mu.Unlock()
38+
39+
if s.store == nil {
40+
s.store = make(map[string]samlsp.Attributes)
41+
}
42+
43+
slog.Debug("setting attributes in store", "id", id, "attrs", attrs)
44+
45+
s.store[id] = attrs
46+
}
47+
48+
func (s *MemoryAttributeStore) Delete(id string) {
49+
s.mu.Lock()
50+
defer s.mu.Unlock()
51+
52+
if s.store == nil {
53+
return
54+
}
55+
56+
slog.Debug("deleting attributes in store", "id", id)
57+
58+
delete(s.store, id)
59+
}

pkg/sp/options.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package sp
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"net/http"
7+
"net/url"
8+
"time"
9+
10+
"github.com/crewjam/saml/samlsp"
11+
)
12+
13+
type ServiceProviderOption func(*ServiceProvider)
14+
15+
func WithMetadataURL(metadata *url.URL) ServiceProviderOption {
16+
return func(s *ServiceProvider) {
17+
// populate metadata either from a metadata URL or from custom values
18+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
19+
defer cancel()
20+
21+
// fetch metadata from URL
22+
idpMetadata, err := samlsp.FetchMetadata(ctx, http.DefaultClient, *metadata)
23+
if err != nil {
24+
slog.Error("error fetching metadata", "error", err)
25+
return
26+
}
27+
28+
s.idpMetadata = idpMetadata
29+
}
30+
}
31+
32+
func WithCustomMetadata(metadata ServiceProviderMetadata) ServiceProviderOption {
33+
return func(s *ServiceProvider) {
34+
// build metadata from provided values
35+
b, err := buildMetadata(metadata.Issuer, metadata.Endpoint, metadata.NameId, metadata.Certificate)
36+
if err != nil {
37+
slog.Error("metadata build error", "error", err)
38+
return
39+
}
40+
41+
idpMetadata, err := samlsp.ParseMetadata(b)
42+
if err != nil {
43+
slog.Error("custom metadata error", "error", err)
44+
return
45+
}
46+
47+
s.idpMetadata = idpMetadata
48+
}
49+
}
50+
51+
func WithClaimMapping(mapping map[string]string) ServiceProviderOption {
52+
return func(s *ServiceProvider) {
53+
s.mapping = mapping
54+
}
55+
}
56+
57+
func WithAttributeStore(store AttributeStore) ServiceProviderOption {
58+
return func(s *ServiceProvider) {
59+
s.store = store
60+
}
61+
}

0 commit comments

Comments
 (0)