-
Notifications
You must be signed in to change notification settings - Fork 1
/
idp.go
246 lines (212 loc) · 7.25 KB
/
idp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
package main
import (
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"github.com/crewjam/saml/logger"
"github.com/crewjam/saml/samlidp"
"github.com/pkg/errors"
"github.com/zenazn/goji"
"golang.org/x/crypto/bcrypt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"time"
)
var key = func() crypto.PrivateKey {
keyData := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0OhbMuizgtbFOfwbK7aURuXhZx6VRuAs3nNibiuifwCGz6u9
yy7bOR0P+zqN0YkjxaokqFgra7rXKCdeABmoLqCC0U+cGmLNwPOOA0PaD5q5xKhQ
4Me3rt/R9C4Ca6k3/OnkxnKwnogcsmdgs2l8liT3qVHP04Oc7Uymq2v09bGb6nPu
fOrkXS9F6mSClxHG/q59AGOWsXK1xzIRV1eu8W2SNdyeFVU1JHiQe444xLoPul5t
InWasKayFsPlJfWNc8EoU8COjNhfo/GovFTHVjh9oUR/gwEFVwifIHihRE0Hazn2
EQSLaOr2LM0TsRsQroFjmwSGgI+X2bfbMTqWOQIDAQABAoIBAFWZwDTeESBdrLcT
zHZe++cJLxE4AObn2LrWANEv5AeySYsyzjRBYObIN9IzrgTb8uJ900N/zVr5VkxH
xUa5PKbOcowd2NMfBTw5EEnaNbILLm+coHdanrNzVu59I9TFpAFoPavrNt/e2hNo
NMGPSdOkFi81LLl4xoadz/WR6O/7N2famM+0u7C2uBe+TrVwHyuqboYoidJDhO8M
w4WlY9QgAUhkPyzZqrl+VfF1aDTGVf4LJgaVevfFCas8Ws6DQX5q4QdIoV6/0vXi
B1M+aTnWjHuiIzjBMWhcYW2+I5zfwNWRXaxdlrYXRukGSdnyO+DH/FhHePJgmlkj
NInADDkCgYEA6MEQFOFSCc/ELXYWgStsrtIlJUcsLdLBsy1ocyQa2lkVUw58TouW
RciE6TjW9rp31pfQUnO2l6zOUC6LT9Jvlb9PSsyW+rvjtKB5PjJI6W0hjX41wEO6
fshFELMJd9W+Ezao2AsP2hZJ8McCF8no9e00+G4xTAyxHsNI2AFTCQcCgYEA5cWZ
JwNb4t7YeEajPt9xuYNUOQpjvQn1aGOV7KcwTx5ELP/Hzi723BxHs7GSdrLkkDmi
Gpb+mfL4wxCt0fK0i8GFQsRn5eusyq9hLqP/bmjpHoXe/1uajFbE1fZQR+2LX05N
3ATlKaH2hdfCJedFa4wf43+cl6Yhp6ZA0Yet1r8CgYEAwiu1j8W9G+RRA5/8/DtO
yrUTOfsbFws4fpLGDTA0mq0whf6Soy/96C90+d9qLaC3srUpnG9eB0CpSOjbXXbv
kdxseLkexwOR3bD2FHX8r4dUM2bzznZyEaxfOaQypN8SV5ME3l60Fbr8ajqLO288
wlTmGM5Mn+YCqOg/T7wjGmcCgYBpzNfdl/VafOROVbBbhgXWtzsz3K3aYNiIjbp+
MunStIwN8GUvcn6nEbqOaoiXcX4/TtpuxfJMLw4OvAJdtxUdeSmEee2heCijV6g3
ErrOOy6EqH3rNWHvlxChuP50cFQJuYOueO6QggyCyruSOnDDuc0BM0SGq6+5g5s7
H++S/wKBgQDIkqBtFr9UEf8d6JpkxS0RXDlhSMjkXmkQeKGFzdoJcYVFIwq8jTNB
nJrVIGs3GcBkqGic+i7rTO1YPkquv4dUuiIn+vKZVoO6b54f+oPBXd4S0BnuEqFE
rdKNuCZhiaE2XD9L/O9KP1fh5bfEcKwazQ23EvpJHBMm8BGC+/YZNw==
-----END RSA PRIVATE KEY-----`)
if os.Getenv("IDP_KEY") != "" {
var err error
keyData, err = ioutil.ReadFile(os.Getenv("IDP_KEY"))
if err != nil {
logger.DefaultLogger.Fatalf("reading idp key: %s", err)
}
}
b, _ := pem.Decode(keyData)
k, err := x509.ParsePKCS1PrivateKey(b.Bytes)
if err != nil {
logger.DefaultLogger.Fatalf("parsing idp key: %s", err)
}
return k
}()
var cert = func() *x509.Certificate {
certData := []byte(`-----BEGIN CERTIFICATE-----
MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5
NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8A
hs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+a
ucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWx
m+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6
D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURN
B2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0O
BBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56
zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5
pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uv
NONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEf
y/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL
/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsb
GFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTL
UzreO96WzlBBMtY=
-----END CERTIFICATE-----`)
if os.Getenv("IDP_CERT") != "" {
var err error
certData, err = ioutil.ReadFile(os.Getenv("IDP_CERT"))
if err != nil {
logger.DefaultLogger.Fatalf("reading idp cert: %s", err)
}
}
b, _ := pem.Decode(certData)
c, err := x509.ParseCertificate(b.Bytes)
if err != nil {
logger.DefaultLogger.Fatalf("parsing idp cert: %s", err)
}
return c
}()
func main() {
logr := logger.DefaultLogger
userJsonFilename := os.Getenv("USER_JSON")
idpBaseUrlString := os.Getenv("IDP_BASE_URL")
serviceURLstr := os.Getenv("SERVICE_METADATA_URL")
loginPageTemplateFile := os.Getenv("LOGIN_PAGE_TEMPLATE")
if len(userJsonFilename) == 0 {
userJsonFilename = "users.json"
}
idpBaseURL, err := url.Parse(idpBaseUrlString)
if err != nil {
logr.Fatalf("cannot parse base URL: %v", err)
}
serviceURL, err := url.Parse(serviceURLstr)
if err != nil {
logr.Fatalf("cannot parse service URL: %v", err)
}
loginPage := readLoginPageTemplate(loginPageTemplateFile, logr)
idpServer, err := samlidp.New(samlidp.Options{
URL: *idpBaseURL,
Key: key,
Logger: logr,
Certificate: cert,
Store: &samlidp.MemoryStore{},
UseNameFormatBasic: true,
LoginFormTemplate: loginPage,
})
if err != nil {
logr.Fatalf("create idp: %s", err)
}
addUsers(userJsonFilename, idpServer, logr)
addService(idpServer, idpBaseURL, serviceURL, logr)
flag.Set("bind", ":"+idpBaseURL.Port())
goji.Handle("/*", idpServer)
goji.Serve()
}
func addService(idpServer *samlidp.Server, idpBaseURL *url.URL, serviceURL *url.URL, logr *log.Logger) {
if serviceURL != nil {
serviceName := "sample-service"
queryMetaData := func() error {
// read saml metadata from url
samlResp, err := http.Get(serviceURL.String())
if err != nil {
return err
}
if samlResp.StatusCode != http.StatusOK {
data, _ := ioutil.ReadAll(samlResp.Body)
return errors.Errorf("status not ok: %d: %s", samlResp.StatusCode, data)
}
req, err := http.NewRequest("PUT", fmt.Sprintf("%s/services/%s", idpBaseURL, serviceName), samlResp.Body)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
data, _ := ioutil.ReadAll(resp.Body)
return errors.Errorf("status not ok: %d: %s", resp.StatusCode, data)
}
return resp.Body.Close()
}
go func() {
var err error
delay := 1 * time.Second
for {
time.Sleep(delay)
err = queryMetaData()
if err != nil {
logr.Printf("get saml metadata from service failed: %v", err)
delay = delay * 2
} else {
service := samlidp.Service{}
_ = idpServer.Store.Get(fmt.Sprintf("/services/%s", serviceName), &service)
logr.Printf("registered service: name=%s entityId=%s", serviceName, service.Metadata.EntityID)
break
}
}
}()
}
}
func addUsers(filename string, idpServer *samlidp.Server, logr *log.Logger) {
f, err := os.Open(filename)
if err != nil {
logr.Fatalf("open %s: %s", filename, err)
}
var users []samlidp.User
dec := json.NewDecoder(f)
err = dec.Decode(&users)
if err != nil {
logr.Fatalf("decode json: %s", err)
}
f.Close()
for _, user := range users {
if user.PlaintextPassword != nil {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(*user.PlaintextPassword), bcrypt.DefaultCost)
user.HashedPassword = hashedPassword
}
err = idpServer.Store.Put("/users/"+user.Name, user)
if err != nil {
logr.Fatalf("put user: %s", err)
}
logr.Printf("created user %s", user.Name)
}
}
func readLoginPageTemplate(filename string, logr *log.Logger) string {
if filename == "" {
return ""
}
bytes, err := os.ReadFile(filename)
if err != nil {
logr.Fatalf("open %s: %s", filename, err)
}
return string(bytes)
}