Skip to content

Commit 719de56

Browse files
committed
Protect h.state with a mutex
If you have even modest levels of concurrency our access to the state map that keeps all of our state is not safe. Wrap this in an RW lock and use it appropriately. This addresses issue 33
1 parent 1de6f4d commit 719de56

File tree

1 file changed

+27
-4
lines changed

1 file changed

+27
-4
lines changed

sign_certd.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"reflect"
3030
"regexp"
3131
"strings"
32+
"sync"
3233
"time"
3334
)
3435

@@ -121,6 +122,7 @@ type certRequestHandler struct {
121122
Config map[string]ssh_ca_util.SignerdConfig
122123
state map[string]certRequest
123124
sshAgentConn io.ReadWriter
125+
stateMutex sync.RWMutex
124126
}
125127

126128
type signingRequest struct {
@@ -325,11 +327,15 @@ func (h *certRequestHandler) saveSigningRequest(config ssh_ca_util.SignerdConfig
325327
if len(requestIDStr) < 12 {
326328
return false, fmt.Errorf("Request id is too short to be useful.")
327329
}
330+
h.stateMutex.RLock()
328331
_, ok = h.state[requestIDStr]
332+
h.stateMutex.RUnlock()
329333
if ok {
330334
return false, fmt.Errorf("Request id '%s' already in use.", requestIDStr)
331335
}
336+
h.stateMutex.Lock()
332337
h.state[requestIDStr] = certRequest
338+
h.stateMutex.Unlock()
333339

334340
// This is the special case of supporting auto-signing.
335341
if config.NumberSignersRequired < 0 {
@@ -401,7 +407,7 @@ type listResponseElement struct {
401407
Serial uint64
402408
Environment string
403409
Reason string
404-
Cert *ssh.Certificate
410+
Cert *ssh.Certificate `json:"-"`
405411
}
406412
type certRequestResponse map[string]listResponseElement
407413

@@ -458,6 +464,8 @@ func (h *certRequestHandler) listPendingRequests(rw http.ResponseWriter, req *ht
458464

459465
foundSomething := false
460466
results := make(certRequestResponse)
467+
h.stateMutex.RLock()
468+
defer h.stateMutex.RUnlock()
461469
for k, v := range h.state {
462470
encodedCert := base64.StdEncoding.EncodeToString(v.request.Marshal())
463471
element := newResponseElement(v.request, encodedCert, v.certSigned, v.certRejected, len(v.signatures), h.Config[v.environment].NumberSignersRequired, v.request.Serial, v.reason, v.environment)
@@ -497,6 +505,8 @@ func (h *certRequestHandler) getRequestStatus(rw http.ResponseWriter, req *http.
497505
certRejected bool
498506
cert string
499507
}
508+
h.stateMutex.RLock()
509+
defer h.stateMutex.RUnlock()
500510
if h.state[requestID].certSigned {
501511
rw.Write([]byte(h.state[requestID].request.Type()))
502512
rw.Write([]byte(" "))
@@ -511,7 +521,9 @@ func (h *certRequestHandler) getRequestStatus(rw http.ResponseWriter, req *http.
511521

512522
func (h *certRequestHandler) signOrRejectRequest(rw http.ResponseWriter, req *http.Request) {
513523
requestID := mux.Vars(req)["requestID"]
524+
h.stateMutex.RLock()
514525
originalRequest, ok := h.state[requestID]
526+
h.stateMutex.RUnlock()
515527
if !ok {
516528
http.Error(rw, "Unknown request id", http.StatusNotFound)
517529
return
@@ -555,7 +567,9 @@ func (h *certRequestHandler) signOrRejectRequest(rw http.ResponseWriter, req *ht
555567
// Verifying that the cert being posted to us here matches the one in the
556568
// request. That is, that an attacker isn't using an old signature to sign a
557569
// new/different request id
570+
h.stateMutex.RLock()
558571
requestedCert := h.state[requestID].request
572+
h.stateMutex.RUnlock()
559573
if !compareCerts(requestedCert, signedCert) {
560574
log.Printf("Signature was valid, but cert didn't match from %s.", req.RemoteAddr)
561575
log.Printf("Orig req: %#v\n", requestedCert)
@@ -577,6 +591,8 @@ func (h *certRequestHandler) signOrRejectRequest(rw http.ResponseWriter, req *ht
577591

578592
func (h *certRequestHandler) rejectRequest(requestID string, signerFp string, envConfig ssh_ca_util.SignerdConfig) error {
579593
log.Printf("Reject received for id %s", requestID)
594+
h.stateMutex.Lock()
595+
defer h.stateMutex.Unlock()
580596
stateInfo := h.state[requestID]
581597
stateInfo.certRejected = true
582598
// this is weird. see: https://code.google.com/p/go/issues/detail?id=3117
@@ -585,10 +601,15 @@ func (h *certRequestHandler) rejectRequest(requestID string, signerFp string, en
585601
}
586602

587603
func (h *certRequestHandler) addConfirmation(requestID string, signerFp string, envConfig ssh_ca_util.SignerdConfig) error {
588-
if h.state[requestID].certRejected {
604+
h.stateMutex.RLock()
605+
certRejected := h.state[requestID].certRejected
606+
h.stateMutex.RUnlock()
607+
if certRejected {
589608
return fmt.Errorf("Attempt to sign a rejected cert.")
590609
}
610+
h.stateMutex.Lock()
591611
h.state[requestID].signatures[signerFp] = true
612+
h.stateMutex.Unlock()
592613

593614
if envConfig.SlackUrl != "" {
594615
slackMsg := fmt.Sprintf("SSH cert %s signed by %s making %d/%d signatures.",
@@ -611,6 +632,8 @@ func (h *certRequestHandler) addConfirmation(requestID string, signerFp string,
611632
}
612633

613634
func (h *certRequestHandler) maybeSignWithCa(requestID string, numSignersRequired int, signingKeyFingerprint string) (bool, error) {
635+
h.stateMutex.Lock()
636+
defer h.stateMutex.Unlock()
614637
if len(h.state[requestID].signatures) >= numSignersRequired {
615638
if h.sshAgentConn == nil {
616639
// This is used for testing. We're effectively disabling working
@@ -664,8 +687,8 @@ func signdFlags() []cli.Flag {
664687
Usage: "HTTP service address",
665688
},
666689
cli.BoolFlag{
667-
Name: "reverse-proxy",
668-
Usage: "Set when service is behind a reverse proxy, like nginx",
690+
Name: "reverse-proxy",
691+
Usage: "Set when service is behind a reverse proxy, like nginx",
669692
EnvVar: "SSH_CERT_AUTHORITY_PROXY",
670693
},
671694
}

0 commit comments

Comments
 (0)