Skip to content

Commit d25bae6

Browse files
committed
Introducing the generate-config subcommand
Bootstrapping new users/requesters is harder than it should be due to having to generating the configuration file on each host. This commit introduces the generate-config subcommand that users can run to at least bootstrap a configuration file. Accompanying this is the /config/environments resource in sign_certd that can return a list of configured environments on the server. This enables generate-config to produce a pretty decent starting point (and in some cases fully configured) configuration file for new users.
1 parent 878af5b commit d25bae6

6 files changed

Lines changed: 119 additions & 3 deletions

File tree

README.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ building and distributing software). The subcommands are:
224224
- sign
225225
- get
226226
- encrypt-key
227+
- generate-config
227228

228229
As you might have guessed by now this means that a server needs to be
229230
running and serving the ssh-cert-authority service. Users that require
@@ -428,7 +429,8 @@ SSH to.
428429
``--generate-rsa`` will generate a 4096 bit RSA key. ``--generate-ecdsa`` will
429430
generate a key from nist's p384 curve. ECDSA support is nonexistent on
430431
OS X hosts unless your users build openssh from scratch (or homebrew).
431-
This is considered painful.
432+
This is considered painful. This may not be a problem on more modern
433+
versions of OS X (i.e. high sierra) but I haven't tried it.
432434

433435
Requesting Certificates
434436
=======================

USAGE.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ blob containing a stage and prod environment. The user has chosen to use
3636
different SSH keys for production and staging, however, this is not
3737
required. The ``SignerUrl`` is the location of the ssh-cert-authority daemon.
3838

39+
Generating this configuration file can be a little cumbersome. The
40+
``ssh-cert-authority`` program has a ``generate-config`` subcommand that
41+
tries to aid in generating this file. New users can run something like ::
42+
43+
ssh-cert-authority generate-config --url https://ssh-ca.example.com
44+
45+
And that will do two things. First, it goes to ssh-ca.example.com and
46+
requests a listing of the server's configured environments (e.g. "stage"
47+
and "prod"). Second it creates the configuration file inserting
48+
https://ssh-ca.example.com as the SignerUrl and $HOME/.ssh/id_rsa.pub as
49+
the PublicKeyPath.
50+
51+
The generated config file is printed to stdout. You can redirect or
52+
manually copy it into the default location of
53+
``~/.ssh_ca/requester_config.json``.
54+
55+
Users using multiple SSH keys or keys other than id_rsa.pub will need to
56+
manually edit the configuration after it is generated.
57+
3958
Making a request
4059
----------------
4160

generate_config.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/cloudtools/ssh-cert-authority/util"
7+
"github.com/codegangsta/cli"
8+
"io/ioutil"
9+
"net/http"
10+
"os"
11+
"strings"
12+
)
13+
14+
func generateConfigFlags() []cli.Flag {
15+
home := os.Getenv("HOME")
16+
if home == "" {
17+
home = "/"
18+
}
19+
return []cli.Flag{
20+
cli.StringFlag{
21+
Name: "url, u",
22+
Value: "",
23+
Usage: "An ssh-cert-authority url (e.g. https://ssh-cert-authority.example.com).",
24+
},
25+
cli.StringFlag{
26+
Name: "key-file, k",
27+
Value: fmt.Sprintf("%s/.ssh/id_rsa.pub", home),
28+
Usage: "Path to your SSH public key. The filename will be inserted into the generated config file.",
29+
},
30+
}
31+
}
32+
33+
func cmdGenerateConfig(c *cli.Context) error {
34+
35+
url := c.String("url")
36+
if url == "" {
37+
return cli.NewExitError("url is a required option.", 1)
38+
}
39+
if !strings.HasSuffix(url, "/") {
40+
url = url + "/"
41+
}
42+
43+
getResp, err := http.Get(url + "config/environments")
44+
if err != nil {
45+
return cli.NewExitError(fmt.Sprintf("Didn't get a valid response: %s", err), 1)
46+
}
47+
48+
getRespBuf, err := ioutil.ReadAll(getResp.Body)
49+
if err != nil {
50+
return cli.NewExitError(fmt.Sprintf("Error reading response body: %s", err), 1)
51+
}
52+
getResp.Body.Close()
53+
if getResp.StatusCode != 200 {
54+
return cli.NewExitError(fmt.Sprintf("Error getting listing of environments: %s", string(getRespBuf)), 1)
55+
}
56+
57+
environments := make([]string, 0)
58+
json.Unmarshal(getRespBuf, &environments)
59+
60+
wholeConfig := make(map[string]ssh_ca_util.RequesterConfig, len(environments))
61+
for _, envName := range environments {
62+
wholeConfig[envName] = ssh_ca_util.RequesterConfig{
63+
PublicKeyPath: c.String("key-file"),
64+
SignerUrl: url,
65+
}
66+
}
67+
result, err := json.MarshalIndent(wholeConfig, "", " ")
68+
if err != nil {
69+
return cli.NewExitError(fmt.Sprintf("Failed to serialize config file: %v", err), 1)
70+
}
71+
fmt.Print(string(result))
72+
return nil
73+
}

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ func main() {
5353
Usage: "Optionally generate and then encrypt an ssh private key",
5454
Action: cmdEncryptKey,
5555
},
56+
{
57+
Name: "generate-config",
58+
Flags: generateConfigFlags(),
59+
Usage: "Try to generate a configuration file for a requester",
60+
Action: cmdGenerateConfig,
61+
},
5662
}
5763
app.Run(os.Args)
5864
}

sign_certd.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,20 @@ func newResponseElement(certBlob string, signed bool, numSignatures, signaturesR
397397
return element
398398
}
399399

400+
func (h *certRequestHandler) listEnvironments(rw http.ResponseWriter, req *http.Request) {
401+
environments := make([]string, 0)
402+
for k, _ := range h.Config {
403+
environments = append(environments, k)
404+
}
405+
result, err := json.Marshal(environments)
406+
if err != nil {
407+
http.Error(rw, fmt.Sprintf("Unable to marshal environment names: %v", err), http.StatusInternalServerError)
408+
return
409+
}
410+
log.Printf("List environments received from '%s'\n", req.RemoteAddr)
411+
rw.Write(result)
412+
}
413+
400414
func (h *certRequestHandler) listPendingRequests(rw http.ResponseWriter, req *http.Request) {
401415
var certRequestID string
402416

@@ -676,6 +690,8 @@ func runSignCertd(config map[string]ssh_ca_util.SignerdConfig, addr string) erro
676690
request := r.Path("/cert/requests/{requestID}").Subrouter()
677691
request.Methods("GET").HandlerFunc(requestHandler.getRequestStatus)
678692
request.Methods("POST", "DELETE").HandlerFunc(requestHandler.signOrRejectRequest)
693+
environments := r.Path("/config/environments").Subrouter()
694+
environments.Methods("GET").HandlerFunc(requestHandler.listEnvironments)
679695
http.ListenAndServe(addr, r)
680696
return nil
681697
}

util/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
)
88

99
type RequesterConfig struct {
10-
PublicKeyPath string
11-
PublicKeyFingerprint string
10+
PublicKeyPath string `json:",omitempty"`
11+
PublicKeyFingerprint string `json:",omitempty"`
1212
SignerUrl string
1313
}
1414

0 commit comments

Comments
 (0)