Skip to content

Commit a71e60e

Browse files
committed
A list subcommand for the CLI
You can now run `ssh-cert-authority list` It presents, somewhat half-assedly, a list of pending, un-expired certificates. The --show-all flag will also show fully signed or expired certs. Kinda fancy.
1 parent 24177e8 commit a71e60e

3 files changed

Lines changed: 128 additions & 5 deletions

File tree

list_requests.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package main
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
"github.com/cloudtools/ssh-cert-authority/util"
8+
"github.com/codegangsta/cli"
9+
"golang.org/x/crypto/ssh"
10+
"io/ioutil"
11+
"net/http"
12+
"os"
13+
"time"
14+
)
15+
16+
func listCertFlags() []cli.Flag {
17+
home := os.Getenv("HOME")
18+
if home == "" {
19+
home = "/"
20+
}
21+
configPath := home + "/.ssh_ca/signer_config.json"
22+
23+
return []cli.Flag{
24+
cli.StringFlag{
25+
Name: "environment",
26+
Value: "",
27+
Usage: "An environment name (e.g. prod)",
28+
},
29+
cli.StringFlag{
30+
Name: "config-file",
31+
Value: configPath,
32+
Usage: "Path to config.json",
33+
},
34+
cli.BoolFlag{
35+
Name: "show-all",
36+
Usage: "Show certs that have already been signed as well",
37+
},
38+
}
39+
}
40+
41+
func listCerts(c *cli.Context) {
42+
43+
configPath := c.String("config-file")
44+
environment := c.String("environment")
45+
showAll := c.Bool("show-all")
46+
47+
allConfig := make(map[string]ssh_ca_util.RequesterConfig)
48+
err := ssh_ca_util.LoadConfig(configPath, &allConfig)
49+
wrongTypeConfig, err := ssh_ca_util.GetConfigForEnv(environment, &allConfig)
50+
if err != nil {
51+
fmt.Println(err)
52+
os.Exit(1)
53+
}
54+
config := wrongTypeConfig.(ssh_ca_util.RequesterConfig)
55+
56+
getResp, err := http.Get(config.SignerUrl + "cert/requests")
57+
if err != nil {
58+
fmt.Println("Didn't get a valid response", err)
59+
os.Exit(1)
60+
}
61+
getRespBuf, err := ioutil.ReadAll(getResp.Body)
62+
if err != nil {
63+
fmt.Println("Error reading response body", err)
64+
os.Exit(1)
65+
}
66+
getResp.Body.Close()
67+
if getResp.StatusCode != 200 {
68+
fmt.Println("Error getting listing of certs", string(getRespBuf))
69+
os.Exit(1)
70+
}
71+
72+
certs := make(certRequestResponse)
73+
json.Unmarshal(getRespBuf, &certs)
74+
for requestID, respElement := range certs {
75+
if showAll || !respElement.Signed {
76+
rawCert, err := base64.StdEncoding.DecodeString(respElement.CertBlob)
77+
if err != nil {
78+
fmt.Println("Trouble base64 decoding response:", err, respElement.CertBlob)
79+
os.Exit(1)
80+
}
81+
pubKey, err := ssh.ParsePublicKey(rawCert)
82+
if err != nil {
83+
fmt.Println("Trouble parsing response:", err)
84+
os.Exit(1)
85+
}
86+
cert := *pubKey.(*ssh.Certificate)
87+
env, ok := cert.Extensions["environment@cloudtools.github.io"]
88+
if !ok {
89+
env = "unknown env"
90+
}
91+
expired := int64(cert.ValidBefore)-int64(time.Now().Unix()) < 1
92+
if !expired || showAll {
93+
expiredMsg := ""
94+
if expired {
95+
expiredMsg = ", \033[91mexpired\033[0m"
96+
}
97+
fmt.Printf("%d %s[%s, %d/%d%s]: %s - %s\n",
98+
respElement.Serial,
99+
requestID,
100+
env,
101+
respElement.NumSignatures,
102+
respElement.SignaturesRequired,
103+
expiredMsg,
104+
cert.KeyId,
105+
cert.Extensions["reason@cloudtools.github.io"],
106+
)
107+
}
108+
}
109+
}
110+
}

main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ func main() {
3434
Usage: "Get a certificate",
3535
Action: getCert,
3636
},
37+
{
38+
Name: "list",
39+
Aliases: []string{"l"},
40+
Flags: listCertFlags(),
41+
Usage: "List pending requests on the server",
42+
Action: listCerts,
43+
},
3744
{
3845
Name: "runserver",
3946
Flags: signdFlags(),

sign_certd.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,15 +268,21 @@ func (h *certRequestHandler) validateCert(cert *ssh.Certificate, authorizedSigne
268268
}
269269

270270
type listResponseElement struct {
271-
Signed bool
272-
CertBlob string
271+
Signed bool
272+
CertBlob string
273+
NumSignatures int
274+
SignaturesRequired int
275+
Serial uint64
273276
}
274277
type certRequestResponse map[string]listResponseElement
275278

276-
func newResponseElement(certBlob string, signed bool) listResponseElement {
279+
func newResponseElement(certBlob string, signed bool, numSignatures, signaturesRequired int, serial uint64) listResponseElement {
277280
var element listResponseElement
278281
element.CertBlob = certBlob
279282
element.Signed = signed
283+
element.NumSignatures = numSignatures
284+
element.SignaturesRequired = signaturesRequired
285+
element.Serial = serial
280286
return element
281287
}
282288

@@ -303,10 +309,10 @@ func (h *certRequestHandler) listPendingRequests(rw http.ResponseWriter, req *ht
303309
req.RemoteAddr, certRequestID)
304310

305311
foundSomething := false
306-
results := make(map[string]listResponseElement)
312+
results := make(certRequestResponse)
307313
for k, v := range h.state {
308314
encodedCert := base64.StdEncoding.EncodeToString(v.request.Marshal())
309-
element := newResponseElement(encodedCert, v.certSigned)
315+
element := newResponseElement(encodedCert, v.certSigned, len(v.signatures), h.Config[v.environment].NumberSignersRequired, v.request.Serial)
310316
// Two ways to use this URL. If caller specified a certRequestId
311317
// then we return only that one. Otherwise everything.
312318
if certRequestID == "" {

0 commit comments

Comments
 (0)