diff --git a/.gitignore b/.gitignore index 5d276348528b6f5f649fe26d5953d72ee3ff6095..2e8401727ef4ac983c0d7861f7ea82d0497cb18f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ config.json cashierd.conf tmp -cashier -cashierd +/cashier +/cashierd signing_key http.log diff --git a/README.md b/README.md index 652a8800a50a00469da5fa4ffb42bccc13ba03db..05a930e6fa96aedc65cd2d9f8744f611c91fa3fd 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,14 @@ For the server you need the following: ## Using cashier Once the server is up and running you'll need to configure your client. -The client is configured using either a [HCL](https://github.com/hashicorp/hcl) configuration file - [example](example-client.conf) - or command-line flags. +The client is configured using either a [HCL](https://github.com/hashicorp/hcl) configuration file - [example](example-client.conf) - or command-line flags. + +- `--ca` CA server (default "http://localhost:10000"). +- `--config` Path to config file (default "~/.cashier.conf"). +- `--key_size` Key size. Ignored for ed25519 keys (default 2048). +- `--key_type` Type of private key to generate - rsa, ecdsa or ed25519 (default "rsa"). +- `--public_file_prefix` Prefix for filename for public key and cert (optional, no default). The public key is put in a file with `.pub` appended to it; the public cert file in a file with `-cert.pub` appended to it. +- `--validity` Key validity (default 24h). Running the `cashier` cli tool will open a browser window at the configured CA address. The CA will redirect to the auth provider for authorisation, and redirect back to the CA where the access token will printed. @@ -259,9 +266,17 @@ Copy the access token. In the terminal where you ran the `cashier` cli paste the The client will then generate a new ssh key-pair and send the public part to the server (along with the access token). Once signed the client will install the key and signed certificate in your ssh agent. When the certificate expires it will be removed automatically from the agent. +If you set `public_file_prefix` then the public key and public cert will be written to the files that start with `public_file_prefix` and end with `.pub` and `-cert.pub` respectively. + +In your `ssh_config` you can load these for a given host with the `IdentityFile` and `CertificateFile`. However prior to OpenSSH version 7.2p1 the latter option didn't exist. +In that case you could specify `~/.ssh/some-identity` as your `IdentityFile` and OpenSSH would look in `~/.ssh/some-identity.pub` and `~/.ssh/some-identity-cert.pub`. + +Starting with 7.2p1 the two options exist in the `ssh_config` and you'll need to use the full paths to them. +Note that like these `ssh_config` options, the `public_file_prefix` supports tilde expansion. + ## Configuring SSH -The ssh client needs no special configuration, just a running ssh-agent. -The ssh server needs to trust the public part of the CA signing key. Add something like the following to your sshd_config: +The ssh client needs no special configuration, just a running `ssh-agent`. +The ssh server needs to trust the public part of the CA signing key. Add something like the following to your `sshd_config`: ``` TrustedUserCAKeys /etc/ssh/ca.pub ``` diff --git a/client/client.go b/client/client.go index 382c53dcb4a6f86b4158becca4756a96f342ed27..e1fb98c635a5af674fee81b65ea00b846e33e89e 100644 --- a/client/client.go +++ b/client/client.go @@ -3,8 +3,10 @@ package client import ( "bytes" "crypto/tls" + "encoding/base64" "encoding/json" "fmt" + "io/ioutil" "net/http" "net/url" "path" @@ -16,6 +18,22 @@ import ( "golang.org/x/crypto/ssh/agent" ) +// SavePublicFiles installs the public part of the cert and key. +func SavePublicFiles(prefix string, cert *ssh.Certificate, pub ssh.PublicKey) error { + if prefix == "" { + return nil + } + pubTxt := ssh.MarshalAuthorizedKey(pub) + certPubTxt := []byte(cert.Type() + " " + base64.StdEncoding.EncodeToString(cert.Marshal())) + + if err := ioutil.WriteFile(prefix+".pub", pubTxt, 0644); err != nil { + return err + } + err := ioutil.WriteFile(prefix+"-cert.pub", certPubTxt, 0644) + + return err +} + // InstallCert adds the private key and signed certificate to the ssh agent. func InstallCert(a agent.Agent, cert *ssh.Certificate, key Key) error { t := time.Unix(int64(cert.ValidBefore), 0) diff --git a/client/config.go b/client/config.go index 1cc9401fc6a2a340e5d5d77979b5926e0e9f2f8e..07bbb8c940ff35e53e8fc0a0c665fea1c3fffe18 100644 --- a/client/config.go +++ b/client/config.go @@ -1,6 +1,7 @@ package client import ( + "github.com/mitchellh/go-homedir" "github.com/spf13/pflag" "github.com/spf13/viper" ) @@ -12,6 +13,7 @@ type Config struct { Keysize int `mapstructure:"key_size"` Validity string `mapstructure:"validity"` ValidateTLSCertificate bool `mapstructure:"validate_tls_certificate"` + PublicFilePrefix string `mapstructure:"public_file_prefix"` } func setDefaults() { @@ -19,6 +21,7 @@ func setDefaults() { viper.BindPFlag("key_type", pflag.Lookup("key_type")) viper.BindPFlag("key_size", pflag.Lookup("key_size")) viper.BindPFlag("validity", pflag.Lookup("validity")) + viper.BindPFlag("public_file_prefix", pflag.Lookup("public_file_prefix")) viper.SetDefault("validateTLSCertificate", true) } @@ -34,5 +37,10 @@ func ReadConfig(path string) (*Config, error) { if err := viper.Unmarshal(c); err != nil { return nil, err } + p, err := homedir.Expand(c.PublicFilePrefix) + if err != nil { + return nil, err + } + c.PublicFilePrefix = p return c, nil } diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go index 26c6cbf4e63d905a8357e8fa656c9d20fe941bc7..53deffd2ab1a8f04b156460f5f9ee99ec80cf021 100644 --- a/cmd/cashier/main.go +++ b/cmd/cashier/main.go @@ -16,12 +16,13 @@ import ( ) var ( - u, _ = user.Current() - cfg = pflag.String("config", path.Join(u.HomeDir, ".cashier.conf"), "Path to config file") - ca = pflag.String("ca", "http://localhost:10000", "CA server") - keysize = pflag.Int("key_size", 2048, "Key size. Ignored for ed25519 keys") - validity = pflag.Duration("validity", time.Hour*24, "Key validity") - keytype = pflag.String("key_type", "rsa", "Type of private key to generate - rsa, ecdsa or ed25519") + u, _ = user.Current() + cfg = pflag.String("config", path.Join(u.HomeDir, ".cashier.conf"), "Path to config file") + ca = pflag.String("ca", "http://localhost:10000", "CA server") + keysize = pflag.Int("key_size", 2048, "Key size. Ignored for ed25519 keys") + validity = pflag.Duration("validity", time.Hour*24, "Key validity") + keytype = pflag.String("key_type", "rsa", "Type of private key to generate - rsa, ecdsa or ed25519") + publicFilePrefix = pflag.String("public_file_prefix", "", "Prefix for filename for public key and cert (optional, no default)") ) func main() { @@ -58,5 +59,8 @@ func main() { if err := client.InstallCert(a, cert, priv); err != nil { log.Fatalln(err) } + if err := client.SavePublicFiles(c.PublicFilePrefix, cert, pub); err != nil { + log.Fatalln(err) + } fmt.Println("Credentials added.") }