daemon.go 6.63 KB
Newer Older
Heiko Reese's avatar
 
Heiko Reese committed
1
2
3
package main

import (
Heiko Reese's avatar
Heiko Reese committed
4
	_ "encoding/json"
Heiko Reese's avatar
 
Heiko Reese committed
5
	"flag"
6
	"fmt"
Heiko Reese's avatar
   
Heiko Reese committed
7
	. "git.scc.kit.edu/KIT-CA/websearch"
Heiko Reese's avatar
 
Heiko Reese committed
8
	"github.com/gorilla/mux"
Heiko Reese's avatar
../..    
Heiko Reese committed
9
	_ "github.com/k0kubun/pp"
Heiko Reese's avatar
Heiko Reese committed
10
	"github.com/satori/go.uuid"
Heiko Reese's avatar
 
Heiko Reese committed
11
12
13
14
	"log"
	"net"
	"net/http"
	"net/http/fcgi"
15
	"net/http/httputil"
Heiko Reese's avatar
../..    
Heiko Reese committed
16
	"path/filepath"
17
	"sort"
Heiko Reese's avatar
 
Heiko Reese committed
18
	"strings"
19
	"time"
Heiko Reese's avatar
 
Heiko Reese committed
20
21
22
23
)

const (
	local = 1 << iota
Heiko Reese's avatar
Heiko Reese committed
24
25
	tcp
	unix
Heiko Reese's avatar
 
Heiko Reese committed
26
27
28
)

var (
Heiko Reese's avatar
   
Heiko Reese committed
29
30
31
32
33
	localaddr        string
	modeArg          string
	mode             int
	ccache           *CertCache
	certRepoDir      string
34
	webrootDir       string
Heiko Reese's avatar
   
Heiko Reese committed
35
36
37
	watcherDone      chan bool
	initialBatchDone chan bool = make(chan bool, 1)
	newFileChan      chan string
38
	AllWatchers      map[int]*AttributeState
Heiko Reese's avatar
 
Heiko Reese committed
39
40
41
)

func init() {
Heiko Reese's avatar
Heiko Reese committed
42
43
	log.SetFlags(0)

Heiko Reese's avatar
   
Heiko Reese committed
44
	// flag parsing
Heiko Reese's avatar
 
Heiko Reese committed
45
46
	flag.StringVar(&modeArg, "mode", "local", "Mode of operation: local (local webserver), tcp (FCGI via localaddr) or unix (FCGI via UNIX socket at localaddr)")
	flag.StringVar(&localaddr, "localaddr", "localhost:8000", "Local bind address (local, tcp) oder filename of UNIX socket (unix)")
47
	flag.StringVar(&certRepoDir, "certdir", "Certificates", "Location of certificate files")
48
	flag.StringVar(&webrootDir, "webroot", "webroot", "Location of static web files")
Heiko Reese's avatar
Heiko Reese committed
49
	//flag.StringVar(&sessionKeyFile, "sessionkeyfile", ".ca-websearch-sessionkeys.json", "Location of secret key used to authenticate and encrypt sessions")
Heiko Reese's avatar
 
Heiko Reese committed
50
51
52
53
54
55
56
57
58
59
	flag.Parse()
	modeArg = strings.TrimSpace(strings.ToLower(modeArg))
	switch modeArg {
	case "local":
		mode = local
	case "tcp":
		mode = tcp
	case "unix":
		mode = unix
	default:
60
		log.Fatal("Unknown mode:", modeArg)
Heiko Reese's avatar
 
Heiko Reese committed
61
62
63
	}
}

64
func redirectHandler(w http.ResponseWriter, r *http.Request) {
Heiko Reese's avatar
Heiko Reese committed
65
66
67
68
	var (
		serial = mux.Vars(r)["serial"]
		dowhat = mux.Vars(r)["dowhat"]
	)
Heiko Reese's avatar
Heiko Reese committed
69

Heiko Reese's avatar
   
Heiko Reese committed
70
71
72
73
	requester := r.Header.Get("X-Forwarded-For")
	if requester == "" {
		requester = r.RemoteAddr
	}
Heiko Reese's avatar
Heiko Reese committed
74

Heiko Reese's avatar
   
Heiko Reese committed
75
	ca, err := GetIssuer(serial, ccache)
Heiko Reese's avatar
Heiko Reese committed
76
77
	if err == nil {
		url := BuildCertificateLink(RedirTemplates[dowhat], ca, serial)
78
		//log.Printf("Redirecting request %s from %s to %s", r.URL.String(), requester, url)
Heiko Reese's avatar
Heiko Reese committed
79
80
81
		http.Redirect(w, r, url, http.StatusFound)
	} else {
		uuid4 := uuid.NewV4().String()
Heiko Reese's avatar
   
Heiko Reese committed
82
		log.Printf("[%s] unable to process request %s from %s: serial %s not in cache", uuid4, r.URL.String(), requester, serial)
Heiko Reese's avatar
Heiko Reese committed
83
84
85
		errormsg := "Invalid serial number " + serial + " (errorid " + uuid4 + ")"
		http.Error(w, errormsg, http.StatusBadRequest)
	}
Heiko Reese's avatar
 
Heiko Reese committed
86
87
}

Heiko Reese's avatar
Heiko Reese committed
88
89
90
91
92
93
94
95
96
97
98
99
100
101
func pubsearchHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
	query := r.Form.Get("q")
	filter := MakePublicSearchFilter(query, AllWatchers[WatchVisibile])
	results := ccache.Filter(filter)
	sort.Sort(results)
	w.Header().Set("cache-control", "no-store")
	w.Header().Set("Content-Type", "application/json")
	w.Write(results.JSONString(AllWatchers))
}

102
func searchHandler(w http.ResponseWriter, r *http.Request) {
103
104
105
106
107
	err := r.ParseForm()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
	query := r.Form.Get("q")
108
	filter := MakeInternalSearchFilter(query)
109
	results := ccache.Filter(filter)
110
	sort.Sort(results)
111
	w.Header().Set("cache-control", "no-store")
112
113
	w.Header().Set("Content-Type", "application/json")
	w.Write(results.JSONString(AllWatchers))
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
func downloadHandler(w http.ResponseWriter, r *http.Request) {
	var (
		format = mux.Vars(r)["format"]
		serial = mux.Vars(r)["serial"]
	)
	cert := ccache.Get(serial)
	if cert == nil {
		uuid4 := uuid.NewV4().String()
		log.Printf("[%s] unable to process request %s, serial %s not in cache", uuid4, r.URL.String(), serial)
		errormsg := "Invalid serial number " + serial + " (errorid " + uuid4 + ")"
		http.Error(w, errormsg, http.StatusBadRequest)
	} else {
		switch format {
		case "pem":
			w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.pem", serial))
			w.Header().Set("Content-Type", "application/x-pem-file")
			w.Write(cert.GetPEM())
		case "der":
			w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.crt", serial))
			w.Header().Set("Content-Type", "application/x-x509-user-cert")
			w.Write(cert.GetDER())
		}
	}
}

141
142
func emailtocertHandler(w http.ResponseWriter, r *http.Request) {
	email := mux.Vars(r)["email"]
143
	// retrieve all certs with that email
144
	results := ccache.IndexEmail.Get(email)
145
146
147
148
149
	// only valid certs
	results = results.Filter(
		func(c *SearchableCert) bool {
			return AllWatchers[WatchValid].Is(c.Serial, Valid) && c.NotAfter.After(time.Now())
		})
150
151
152
	w.Header().Set("cache-control", "no-store")
	w.Header().Set("Content-Type", "application/json")
	w.Write(results.JSONString(AllWatchers))
Heiko Reese's avatar
reorg    
Heiko Reese committed
153
154
}

Heiko Reese's avatar
   
Heiko Reese committed
155
156
func main() {
	var err error
Heiko Reese's avatar
 
Heiko Reese committed
157

Heiko Reese's avatar
   
Heiko Reese committed
158
	// create watcher for new certificates
159
160
	allCertsDirectory := filepath.Join(certRepoDir, ".archive")
	log.Println("Starting filewatcher for directory", allCertsDirectory)
Heiko Reese's avatar
   
Heiko Reese committed
161
	ccache = CertArchiveWatcher(allCertsDirectory, initialBatchDone)
162
	<-initialBatchDone
Heiko Reese's avatar
Heiko Reese committed
163
	log.Println(ccache.Len(), "certificates have been loaded into the certificate cache.")
Heiko Reese's avatar
 
Heiko Reese committed
164

165
	// handle Validity change
166
	AllWatchers = CreateAllWatchers(certRepoDir)
167

Heiko Reese's avatar
../..    
Heiko Reese committed
168
	// create http interface
Heiko Reese's avatar
 
Heiko Reese committed
169
170
	r := mux.NewRouter()

171
	// add redirect handler
172
173
174
	r.Path("/redirect/{dowhat:getcert|installcert}/{serial:[0-9]+}").
		Methods("GET").
		HandlerFunc(redirectHandler)
175

Heiko Reese's avatar
   
Heiko Reese committed
176
177
178
179
	// add public search handler
	r.Path("/pubsearch/v1/json").
		Methods("GET").
		Queries("q", "").
Heiko Reese's avatar
Heiko Reese committed
180
		HandlerFunc(pubsearchHandler)
Heiko Reese's avatar
   
Heiko Reese committed
181
182
183

	// add internal search handler
	r.Path("/search/v1/json").
184
		Methods("GET").
185
		Queries("q", "").
186
		HandlerFunc(searchHandler)
Heiko Reese's avatar
 
Heiko Reese committed
187

188
	// add download handler
189
190
191
192
	r.Path("/download/{format:pem|der|text}/{serial:[0-9]+}").
		Methods("GET").
		HandlerFunc(downloadHandler)

193
	// add emailtocert handler
194
	r.Path("/emailtocert/v1/{email:.*@.*}").
Heiko Reese's avatar
reorg    
Heiko Reese committed
195
		Methods("GET").
196
		HandlerFunc(emailtocertHandler)
Heiko Reese's avatar
reorg    
Heiko Reese committed
197

198
199
200
201
202
203
204
205
	// add request dumping handler
	r.Path("/dumpreq").
		Methods("GET").
		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			requestDump, err := httputil.DumpRequest(r, true)
			if err != nil {
				log.Print(err)
			}
KIT-CA Websearch and Redirect's avatar
KIT-CA Websearch and Redirect committed
206
207
			w.Header().Set("content-type", "text/plain")
			w.Write(requestDump)
208
209
		})

Heiko Reese's avatar
Heiko Reese committed
210
211
212
	// add handler for static files
	r.PathPrefix("/").
		Handler(http.FileServer(http.Dir(webrootDir)))
213

Heiko Reese's avatar
Heiko Reese committed
214
	// DEBUG: add notfound handler
KIT-CA Websearch and Redirect's avatar
KIT-CA Websearch and Redirect committed
215
216
217
218
219
220
221
222
223
224
	r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestDump, err := httputil.DumpRequest(r, true)
		if err != nil {
			log.Print(err)
		}
		w.Header().Set("content-type", "text/plain")
		w.Write(requestDump)
		http.NotFound(w, r)
	})

Heiko Reese's avatar
 
Heiko Reese committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
	log.Printf("Serving %s via %s", modeArg, localaddr)
	switch mode {
	case local:
		err = http.ListenAndServe(localaddr, r)
	case tcp:
		listener, err := net.Listen("tcp", localaddr)
		if err != nil {
			log.Fatal(err)
		}
		defer listener.Close()
		err = fcgi.Serve(listener, r)
	case unix:
		err = fcgi.Serve(nil, r)
	}
	if err != nil {
		log.Fatal(err)
	}
}