Skip to content

Commit 244d0fa

Browse files
committed
Support auto-signing requests
If so configured the server can now automatically sign requests without requiring any explicit signatures from signers.
1 parent 0d39a21 commit 244d0fa

4 files changed

Lines changed: 78 additions & 34 deletions

File tree

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ Effectively the format is::
282282

283283
- ``NumberSignersRequired``: The number of people that must sign a request
284284
before the request is considered complete and signed by the authority.
285+
If this field is < 0 valid certificate requests will be automatically
286+
signed at request time. It is highly recommended that if auto signing
287+
is enabled a ``MaxCertLifetime`` be specified.
285288
- ``MaxCertLifetime``: The maximum duration certificate, measured from Now()
286289
in seconds, that is permitted. The default is 0, meaning unlimited. A
287290
value of 86400 would mean that the server will reject requests for

client/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func (req *CertRequest) PostToWeb(requestParameters url.Values) (string, error)
192192
if err != nil {
193193
return "", err
194194
}
195-
if resp.StatusCode == 201 {
195+
if resp.StatusCode == 201 || resp.StatusCode == 202 {
196196
return string(respBuf), nil
197197
} else {
198198
return "", fmt.Errorf("Cert request rejected: %s", string(respBuf))

sign_certd.go

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func (h *certRequestHandler) createSigningRequest(rw http.ResponseWriter, req *h
140140
requestIDStr := base32.StdEncoding.EncodeToString(requestID)
141141
nextSerial := <-h.NextSerial
142142

143-
err = h.saveSigningRequest(config, environment, reason, requestIDStr, nextSerial, cert)
143+
signed, err := h.saveSigningRequest(config, environment, reason, requestIDStr, nextSerial, cert)
144144
if err != nil {
145145
http.Error(rw, fmt.Sprintf("Request not made: %v", err), http.StatusBadRequest)
146146
return
@@ -150,8 +150,6 @@ func (h *certRequestHandler) createSigningRequest(rw http.ResponseWriter, req *h
150150
log.Printf("Cert request serial %d id %s env %s from %s (%s) @ %s principals %v valid from %d to %d for '%s'\n",
151151
cert.Serial, requestIDStr, environment, requesterFp, config.AuthorizedUsers[requesterFp],
152152
req.RemoteAddr, cert.ValidPrincipals, cert.ValidAfter, cert.ValidBefore, reason)
153-
rw.WriteHeader(http.StatusCreated)
154-
rw.Write([]byte(requestIDStr))
155153

156154
if config.SlackUrl != "" {
157155
slackMsg := fmt.Sprintf("SSH cert request from %s with id %s for %s", config.AuthorizedUsers[requesterFp], requestIDStr, reason)
@@ -161,16 +159,30 @@ func (h *certRequestHandler) createSigningRequest(rw http.ResponseWriter, req *h
161159
}
162160
}
163161

162+
var returnStatus int
163+
if signed {
164+
slackMsg := fmt.Sprintf("SSH cert request %s auto signed.", requestID)
165+
err := ssh_ca_client.PostToSlack(config.SlackUrl, config.SlackChannel, slackMsg)
166+
if err != nil {
167+
log.Printf("Unable to post to slack for %s: %v", requestID, err)
168+
}
169+
returnStatus = http.StatusAccepted
170+
} else {
171+
returnStatus = http.StatusCreated
172+
}
173+
rw.WriteHeader(returnStatus)
174+
rw.Write([]byte(requestIDStr))
175+
164176
return
165177
}
166178

167-
func (h *certRequestHandler) saveSigningRequest(config ssh_ca_util.SignerdConfig, environment, reason, requestIDStr string, requestSerial uint64, cert *ssh.Certificate) error {
179+
func (h *certRequestHandler) saveSigningRequest(config ssh_ca_util.SignerdConfig, environment, reason, requestIDStr string, requestSerial uint64, cert *ssh.Certificate) (bool, error) {
168180
requesterFp := ssh_ca_util.MakeFingerprint(cert.SignatureKey.Marshal())
169181

170182
maxValidBefore := uint64(time.Now().Add(time.Duration(config.MaxCertLifetime) * time.Second).Unix())
171183

172184
if config.MaxCertLifetime != 0 && cert.ValidBefore > maxValidBefore {
173-
return fmt.Errorf("Certificate is valid longer than maximum permitted by configuration %d > %d",
185+
return false, fmt.Errorf("Certificate is valid longer than maximum permitted by configuration %d > %d",
174186
cert.ValidBefore, maxValidBefore)
175187
}
176188

@@ -179,36 +191,44 @@ func (h *certRequestHandler) saveSigningRequest(config ssh_ca_util.SignerdConfig
179191
var ok bool
180192
cert.KeyId, ok = config.AuthorizedUsers[requesterFp]
181193
if !ok {
182-
return fmt.Errorf("Requester fingerprint (%s) not found in config", requesterFp)
194+
return false, fmt.Errorf("Requester fingerprint (%s) not found in config", requesterFp)
183195
}
184196

185197
if requestSerial == 0 {
186-
return fmt.Errorf("Serial number not set.")
198+
return false, fmt.Errorf("Serial number not set.")
187199
}
188200
cert.Serial = requestSerial
189201

190202
certRequest := newcertRequest()
191203
certRequest.request = cert
192204
if environment == "" {
193-
return fmt.Errorf("Environment is a required field")
205+
return false, fmt.Errorf("Environment is a required field")
194206
}
195207
certRequest.environment = environment
196208

197209
if reason == "" {
198-
return fmt.Errorf("Reason is a required field")
210+
return false, fmt.Errorf("Reason is a required field")
199211
}
200212
certRequest.reason = reason
201213

202214
if len(requestIDStr) < 12 {
203-
return fmt.Errorf("Request id is too short to be useful.")
215+
return false, fmt.Errorf("Request id is too short to be useful.")
204216
}
205217
_, ok = h.state[requestIDStr]
206218
if ok {
207-
return fmt.Errorf("Request id '%s' already in use.", requestIDStr)
219+
return false, fmt.Errorf("Request id '%s' already in use.", requestIDStr)
208220
}
209221
h.state[requestIDStr] = certRequest
210222

211-
return nil
223+
// This is the special case of supporting auto-signing.
224+
if config.NumberSignersRequired < 0 {
225+
signed, err := h.maybeSignWithCa(requestIDStr, config.NumberSignersRequired, config.SigningKeyFingerprint)
226+
if signed && err == nil {
227+
return true, nil
228+
}
229+
}
230+
231+
return false, nil
212232
}
213233

214234
func (h *certRequestHandler) extractCertFromRequest(req *http.Request) (*ssh.Certificate, error) {
@@ -391,7 +411,7 @@ func (h *certRequestHandler) signOrRejectRequest(rw http.ResponseWriter, req *ht
391411
log.Printf("Signature for serial %d id %s received from %s (%s) @ %s and determined valid\n",
392412
signedCert.Serial, requestID, signerFp, envConfig.AuthorizedSigners[signerFp], req.RemoteAddr)
393413
if req.Method == "POST" {
394-
err = h.addConfirmation(requestID, signerFp, envConfig, true)
414+
err = h.addConfirmation(requestID, signerFp, envConfig)
395415
} else {
396416
err = h.rejectRequest(requestID, signerFp, envConfig)
397417
}
@@ -409,7 +429,7 @@ func (h *certRequestHandler) rejectRequest(requestID string, signerFp string, en
409429
return nil
410430
}
411431

412-
func (h *certRequestHandler) addConfirmation(requestID string, signerFp string, envConfig ssh_ca_util.SignerdConfig, actuallySign bool) error {
432+
func (h *certRequestHandler) addConfirmation(requestID string, signerFp string, envConfig ssh_ca_util.SignerdConfig) error {
413433
if h.state[requestID].certRejected {
414434
return fmt.Errorf("Attempt to sign a rejected cert.")
415435
}
@@ -424,7 +444,7 @@ func (h *certRequestHandler) addConfirmation(requestID string, signerFp string,
424444
log.Printf("Unable to post to slack for %s: %v", requestID, err)
425445
}
426446
}
427-
signed, err := h.maybeSignWithCa(requestID, envConfig.NumberSignersRequired, envConfig.SigningKeyFingerprint, actuallySign)
447+
signed, err := h.maybeSignWithCa(requestID, envConfig.NumberSignersRequired, envConfig.SigningKeyFingerprint)
428448
if signed && err == nil {
429449
slackMsg := fmt.Sprintf("SSH cert request %s fully signed.", requestID)
430450
err := ssh_ca_client.PostToSlack(envConfig.SlackUrl, envConfig.SlackChannel, slackMsg)
@@ -435,11 +455,12 @@ func (h *certRequestHandler) addConfirmation(requestID string, signerFp string,
435455
return err
436456
}
437457

438-
func (h *certRequestHandler) maybeSignWithCa(requestID string, numSignersRequired int, signingKeyFingerprint string, actuallySign bool) (bool, error) {
458+
func (h *certRequestHandler) maybeSignWithCa(requestID string, numSignersRequired int, signingKeyFingerprint string) (bool, error) {
439459
if len(h.state[requestID].signatures) >= numSignersRequired {
440-
if !actuallySign {
460+
if h.sshAgentConn == nil {
441461
// This is used for testing. We're effectively disabling working
442462
// with the ssh agent to avoid needing to mock it.
463+
log.Print("ssh agent uninitialized, will not attempt signing. This is normal in unittests")
443464
return true, nil
444465
}
445466
log.Printf("Received %d signatures for %s, signing now.\n", len(h.state[requestID].signatures), requestID)

sign_certd_test.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func TestRejectRequest(t *testing.T) {
7878
t.Fatalf("Parsing canned cert failed: %v", err)
7979
}
8080
cert := pubKey.(*ssh.Certificate)
81-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
81+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
8282
if err != nil {
8383
t.Fatalf("Should have succeeded. Failed with: %v", err)
8484
}
@@ -87,7 +87,7 @@ func TestRejectRequest(t *testing.T) {
8787
if err != nil {
8888
t.Fatalf("Should have succeeded. Failed with: %v", err)
8989
}
90-
err = requestHandler.addConfirmation("DEADBEEFDEADBEEF", "23:10:8d:d0:54:90:d5:d1:2e:4d:05:fe:4b:54:29:e4", envConfig, false)
90+
err = requestHandler.addConfirmation("DEADBEEFDEADBEEF", "23:10:8d:d0:54:90:d5:d1:2e:4d:05:fe:4b:54:29:e4", envConfig)
9191
if err == nil {
9292
t.Fatalf("Sign after reject should fail.")
9393
}
@@ -104,12 +104,12 @@ func TestRejectRequestAfterSigning(t *testing.T) {
104104
t.Fatalf("Parsing canned cert failed: %v", err)
105105
}
106106
cert := pubKey.(*ssh.Certificate)
107-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
107+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
108108
if err != nil {
109109
t.Fatalf("Should have succeeded. Failed with: %v", err)
110110
}
111111

112-
err = requestHandler.addConfirmation("DEADBEEFDEADBEEF", "23:10:8d:d0:54:90:d5:d1:2e:4d:05:fe:4b:54:29:e4", envConfig, false)
112+
err = requestHandler.addConfirmation("DEADBEEFDEADBEEF", "23:10:8d:d0:54:90:d5:d1:2e:4d:05:fe:4b:54:29:e4", envConfig)
113113
if err != nil {
114114
t.Fatalf("Sign should have worked. It failed: %v", err)
115115
}
@@ -119,7 +119,7 @@ func TestRejectRequestAfterSigning(t *testing.T) {
119119
t.Fatalf("Should have succeeded. Failed with: %v", err)
120120
}
121121

122-
err = requestHandler.addConfirmation("DEADBEEFDEADBEEF", "23:10:8d:d0:54:90:d5:d1:2e:4d:05:fe:4b:54:29:e4", envConfig, false)
122+
err = requestHandler.addConfirmation("DEADBEEFDEADBEEF", "23:10:8d:d0:54:90:d5:d1:2e:4d:05:fe:4b:54:29:e4", envConfig)
123123
if err == nil {
124124
t.Fatalf("Sign after reject should fail.")
125125
}
@@ -136,7 +136,7 @@ func TestSaveForeverCertDisallowed(t *testing.T) {
136136
t.Fatalf("Parsing canned cert failed: %v", err)
137137
}
138138
cert := pubKey.(*ssh.Certificate)
139-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
139+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
140140
if err == nil {
141141
t.Fatalf("Should have failed because cert never expires.")
142142
}
@@ -146,7 +146,7 @@ func TestSaveForeverCertDisallowed(t *testing.T) {
146146
t.Fatalf("Parsing canned cert failed: %v", err)
147147
}
148148
cert = pubKey.(*ssh.Certificate)
149-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
149+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
150150
if err == nil {
151151
t.Fatalf("Should have failed because cert expires in 2025.")
152152
}
@@ -163,7 +163,7 @@ func TestSaveForeverCertAllowed(t *testing.T) {
163163
t.Fatalf("Parsing canned cert failed: %v", err)
164164
}
165165
cert := pubKey.(*ssh.Certificate)
166-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
166+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
167167
if err != nil {
168168
t.Fatalf("Should have worked, failed with: %v", err)
169169
}
@@ -173,12 +173,32 @@ func TestSaveForeverCertAllowed(t *testing.T) {
173173
t.Fatalf("Parsing canned cert failed: %v", err)
174174
}
175175
cert = pubKey.(*ssh.Certificate)
176-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF2", 1, cert)
176+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF2", 1, cert)
177177
if err != nil {
178178
t.Fatalf("Should have worked, failed with: %v", err)
179179
}
180180
}
181181

182+
func TestSaveRequestAutoSign(t *testing.T) {
183+
allConfig := SetupSignerdConfig(-1, 0)
184+
environment := "testing"
185+
envConfig := allConfig[environment]
186+
requestHandler := makeCertRequestHandler(allConfig)
187+
188+
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(boringUserCertString))
189+
if err != nil {
190+
t.Fatalf("Parsing canned cert failed: %v", err)
191+
}
192+
cert := pubKey.(*ssh.Certificate)
193+
signed, err := requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
194+
if err != nil {
195+
t.Fatalf("Should have succeeded. Failed with: %v", err)
196+
}
197+
if !signed {
198+
t.Fatal("Should have auto signed. But we didn't.")
199+
}
200+
}
201+
182202
func TestSaveRequestValidCert(t *testing.T) {
183203
allConfig := SetupSignerdConfig(1, 0)
184204
environment := "testing"
@@ -190,29 +210,29 @@ func TestSaveRequestValidCert(t *testing.T) {
190210
t.Fatalf("Parsing canned cert failed: %v", err)
191211
}
192212
cert := pubKey.(*ssh.Certificate)
193-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
213+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
194214
if err != nil {
195215
t.Fatalf("Should have succeeded. Failed with: %v", err)
196216
}
197217

198-
err = requestHandler.saveSigningRequest(envConfig, environment, "", "DEADBEEFDEAD1111", 1, cert)
218+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "", "DEADBEEFDEAD1111", 1, cert)
199219
if err == nil {
200220
t.Fatalf("Should have failed, reason was missing.")
201221
}
202-
err = requestHandler.saveSigningRequest(envConfig, "", "reason: testing", "DEADBEEFDEAD2222", 1, cert)
222+
_, err = requestHandler.saveSigningRequest(envConfig, "", "reason: testing", "DEADBEEFDEAD2222", 1, cert)
203223
if err == nil {
204224
t.Fatalf("Should have failed, environment was missing.")
205225
}
206-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "EEEFDF", 1, cert)
226+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "EEEFDF", 1, cert)
207227
if err == nil {
208228
t.Fatalf("Should have failed with invalid request id (too short).")
209229
}
210230

211-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "IAM_A_DUPLICATE_ID", 1, cert)
231+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "IAM_A_DUPLICATE_ID", 1, cert)
212232
if err != nil {
213233
t.Fatalf("Should have succeeded. Failed with: %v", err)
214234
}
215-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "IAM_A_DUPLICATE_ID", 1, cert)
235+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "IAM_A_DUPLICATE_ID", 1, cert)
216236
if err == nil {
217237
t.Fatalf("Should have failed with duplicate error")
218238
}
@@ -229,7 +249,7 @@ func TestSaveRequestInvalidCert(t *testing.T) {
229249
t.Fatalf("Parsing canned cert failed: %v", err)
230250
}
231251
cert := pubKey.(*ssh.Certificate)
232-
err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
252+
_, err = requestHandler.saveSigningRequest(envConfig, environment, "reason: testing", "DEADBEEFDEADBEEF", 1, cert)
233253
if err == nil {
234254
t.Fatalf("Should have failed with fingerprint not in list error.")
235255
}

0 commit comments

Comments
 (0)