From 369c103c58ada110e0f48ae864d2cec9a6e435f4 Mon Sep 17 00:00:00 2001 From: Niall Sheridan Date: Sat, 28 May 2016 19:59:04 +0100 Subject: Use a simple config file for configuring the client. --- README.md | 8 ++++---- cmd/cashier/config.go | 33 +++++++++++++++++++++++++++++++++ cmd/cashier/main.go | 35 +++++++++++++++++++++-------------- example-client.cfg | 4 ++++ example-server.json | 24 ++++++++++++++++++++++++ exampleconfig.json | 24 ------------------------ 6 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 cmd/cashier/config.go create mode 100644 example-client.cfg create mode 100644 example-server.json delete mode 100644 exampleconfig.json diff --git a/README.md b/README.md index 29729b7..0eab987 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ The user can now ssh to the production machine, and continue to ssh to any machi # Usage Cashier comes in two parts, a [cli](cmd/cashier) and a [server](cmd/cashierd). -The client is configured using command-line flags. -The server is configured using a JSON configuration file - [example](exampleconfig.json). +The client is configured using a [HCL](https://github.com/hashicorp/hcl) configuration file - [example](example-client.cfg). +The server is configured using a JSON configuration file - [example](example-server.json). For the server you need the following: - A new ssh private key. Generate one in the usual way using `ssh-keygen -f ssh_ca` - this is your CA signing key. At this time Cashier supports RSA, ECDSA and Ed25519 keys. *Important* This key should be kept safe - *ANY* ssh key signed with this key will be able to access your machines. @@ -50,11 +50,11 @@ For the server you need the following: ``` go get github.com/cashier/cmd/... ``` -2. Create a signing key with `ssh-keygen` and a [config.json](exampleconfig.json) +2. Create a signing key with `ssh-keygen` and a [config.json](example-server.json) 3. Run the cashier server with `cashierd` and the cli with `cashier`. ## Using docker -1. Create a signing key with `ssh-keygen` and a [config.json](exampleconfig.json) +1. Create a signing key with `ssh-keygen` and a [config.json](example-server.json) 2. Run ``` docker run -it --rm -p 10000:10000 --name cashier -v $(pwd):/cashier nsheridan/cashier diff --git a/cmd/cashier/config.go b/cmd/cashier/config.go new file mode 100644 index 0000000..b6e583a --- /dev/null +++ b/cmd/cashier/config.go @@ -0,0 +1,33 @@ +package main + +import ( + "github.com/spf13/viper" +) + +type config struct { + CA string `mapstructure:"ca"` + Keytype string `mapstructure:"key_type"` + Keysize int `mapstructure:"key_size"` + Validity string `mapstructure:"validity"` +} + +func setDefaults() { + viper.SetDefault("ca", "http://localhost:10000") + viper.SetDefault("key_type", "rsa") + viper.SetDefault("key_size", 2048) + viper.SetDefault("validity", "24h") +} + +func readConfig(path string) (*config, error) { + setDefaults() + viper.SetConfigFile(path) + viper.SetConfigType("hcl") + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + c := &config{} + if err := viper.Unmarshal(c); err != nil { + return nil, err + } + return c, nil +} diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go index 8bcc3e7..2bac63a 100644 --- a/cmd/cashier/main.go +++ b/cmd/cashier/main.go @@ -10,6 +10,8 @@ import ( "net" "net/http" "os" + "os/user" + "path" "time" "github.com/nsheridan/cashier/lib" @@ -19,10 +21,8 @@ import ( ) var ( - ca = flag.String("ca", "http://localhost:10000", "CA server") - keybits = flag.Int("bits", 2048, "Key size. Ignored for ed25519 keys") - validity = flag.Duration("validity", time.Hour*24, "Key validity") - keytype = flag.String("key_type", "rsa", "Type of private key to generate - rsa, ecdsa or ed25519") + u, _ = user.Current() + cfg = flag.String("config", path.Join(u.HomeDir, ".cashier.cfg"), "Path to config file") ) func installCert(a agent.Agent, cert *ssh.Certificate, key key) error { @@ -37,8 +37,8 @@ func installCert(a agent.Agent, cert *ssh.Certificate, key key) error { return nil } -func send(s []byte, token string) (*lib.SignResponse, error) { - req, err := http.NewRequest("POST", *ca+"/sign", bytes.NewReader(s)) +func send(s []byte, token, ca string) (*lib.SignResponse, error) { + req, err := http.NewRequest("POST", ca+"/sign", bytes.NewReader(s)) if err != nil { return nil, err } @@ -65,17 +65,21 @@ func send(s []byte, token string) (*lib.SignResponse, error) { return c, nil } -func sign(pub ssh.PublicKey, token string) (*ssh.Certificate, error) { +func sign(pub ssh.PublicKey, token string, conf *config) (*ssh.Certificate, error) { + validity, err := time.ParseDuration(conf.Validity) + if err != nil { + return nil, err + } marshaled := ssh.MarshalAuthorizedKey(pub) marshaled = marshaled[:len(marshaled)-1] s, err := json.Marshal(&lib.SignRequest{ Key: string(marshaled), - ValidUntil: time.Now().Add(*validity), + ValidUntil: time.Now().Add(validity), }) if err != nil { return nil, err } - resp, err := send(s, token) + resp, err := send(s, token, conf.CA) if err != nil { return nil, err } @@ -95,13 +99,16 @@ func sign(pub ssh.PublicKey, token string) (*ssh.Certificate, error) { func main() { flag.Parse() - - fmt.Printf("Your browser has been opened to visit %s\n", *ca) - if err := browser.OpenURL(*ca); err != nil { + c, err := readConfig(*cfg) + if err != nil { + log.Fatalf("Error parsing config file: %v\n", err) + } + fmt.Printf("Your browser has been opened to visit %s\n", c.CA) + if err := browser.OpenURL(c.CA); err != nil { fmt.Println("Error launching web browser. Go to the link in your web browser") } fmt.Println("Generating new key pair") - priv, pub, err := generateKey(*keytype, *keybits) + priv, pub, err := generateKey(c.Keytype, c.Keysize) if err != nil { log.Fatalln("Error generating key pair: ", err) } @@ -110,7 +117,7 @@ func main() { var token string fmt.Scanln(&token) - cert, err := sign(pub, token) + cert, err := sign(pub, token, c) if err != nil { log.Fatalln(err) } diff --git a/example-client.cfg b/example-client.cfg new file mode 100644 index 0000000..b9e8232 --- /dev/null +++ b/example-client.cfg @@ -0,0 +1,4 @@ +ca = "https://sshca.example.com" // Address of the cashierd CA +key_type = "rsa" // Type of ssh key to generate - rsa, ecdsa, ed25519 +key_size = 2048 // Size of key to generate. ecdsa must be one of 256, 384, 521. This value is ignored for ed25519 keys. +validity = "24h" // How long the cert will be valid for. Must be a valid go time.Duration. diff --git a/example-server.json b/example-server.json new file mode 100644 index 0000000..1ac84f2 --- /dev/null +++ b/example-server.json @@ -0,0 +1,24 @@ +{ + "server": { + "use_tls": true, + "tls_key": "server.key", + "tls_cert": "server.crt", + "port": 443, + "cookie_secret": "supersecret" + }, + "auth": { + "provider": "google", + "oauth_client_id": "nnnnnnnnnnnnnnnn.apps.googleusercontent.com", + "oauth_client_secret": "yyyyyyyyyyyyyyyyyyyyyy", + "oauth_callback_url": "https://sshca.example.com/auth/callback", + "provider_opts": { + "domain": "example.com" + } + }, + "ssh": { + "signing_key": "signing_key", + "additional_principals": ["ec2-user", "ubuntu"], + "max_age": "720h", + "permissions": ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc"] + } +} diff --git a/exampleconfig.json b/exampleconfig.json deleted file mode 100644 index 1ac84f2..0000000 --- a/exampleconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "server": { - "use_tls": true, - "tls_key": "server.key", - "tls_cert": "server.crt", - "port": 443, - "cookie_secret": "supersecret" - }, - "auth": { - "provider": "google", - "oauth_client_id": "nnnnnnnnnnnnnnnn.apps.googleusercontent.com", - "oauth_client_secret": "yyyyyyyyyyyyyyyyyyyyyy", - "oauth_callback_url": "https://sshca.example.com/auth/callback", - "provider_opts": { - "domain": "example.com" - } - }, - "ssh": { - "signing_key": "signing_key", - "additional_principals": ["ec2-user", "ubuntu"], - "max_age": "720h", - "permissions": ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc"] - } -} -- cgit v1.2.3 From f04bfc498bced76485f8c164f9969e0ed9de7519 Mon Sep 17 00:00:00 2001 From: Niall Sheridan Date: Sat, 28 May 2016 20:11:10 +0100 Subject: Fix tests --- cmd/cashier/client_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cmd/cashier/client_test.go b/cmd/cashier/client_test.go index 492f4fc..426128c 100644 --- a/cmd/cashier/client_test.go +++ b/cmd/cashier/client_test.go @@ -58,8 +58,7 @@ func TestSignGood(t *testing.T) { fmt.Fprintln(w, string(j)) })) defer ts.Close() - *ca = ts.URL - _, err := send([]byte(`{}`), "token") + _, err := send([]byte(`{}`), "token", ts.URL) if err != nil { t.Fatal(err) } @@ -67,7 +66,11 @@ func TestSignGood(t *testing.T) { if err != nil { t.Fatal(err) } - cert, err := sign(k, "token") + c := &config{ + CA: ts.URL, + Validity: "24h", + } + cert, err := sign(k, "token", c) if cert == nil && err != nil { t.Fatal(err) } @@ -83,8 +86,7 @@ func TestSignBad(t *testing.T) { fmt.Fprintln(w, string(j)) })) defer ts.Close() - *ca = ts.URL - _, err := send([]byte(`{}`), "token") + _, err := send([]byte(`{}`), "token", ts.URL) if err != nil { t.Fatal(err) } @@ -92,7 +94,11 @@ func TestSignBad(t *testing.T) { if err != nil { t.Fatal(err) } - cert, err := sign(k, "token") + c := &config{ + CA: ts.URL, + Validity: "24h", + } + cert, err := sign(k, "token", c) if cert != nil && err == nil { t.Fatal(err) } -- cgit v1.2.3 From a5783a4ea89a1a7e7469bcb75b9276d81d7b3aee Mon Sep 17 00:00:00 2001 From: Patrick O'Doherty Date: Sat, 28 May 2016 21:22:38 +0100 Subject: Add validate_tls_certificate option to client config To allow for easier development on localhost where one cannot get a root-CA signed TLS certificate, add a new validate_tls_certificate option to the configuration file which optionally allows for certificate chain checking to be disabled. --- cmd/cashier/config.go | 10 ++++++---- cmd/cashier/main.go | 10 +++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/cashier/config.go b/cmd/cashier/config.go index b6e583a..1196cbd 100644 --- a/cmd/cashier/config.go +++ b/cmd/cashier/config.go @@ -5,10 +5,11 @@ import ( ) type config struct { - CA string `mapstructure:"ca"` - Keytype string `mapstructure:"key_type"` - Keysize int `mapstructure:"key_size"` - Validity string `mapstructure:"validity"` + CA string `mapstructure:"ca"` + Keytype string `mapstructure:"key_type"` + Keysize int `mapstructure:"key_size"` + Validity string `mapstructure:"validity"` + ValidateTLSCertificate bool `mapstructure:"validate_tls_certificate"` } func setDefaults() { @@ -16,6 +17,7 @@ func setDefaults() { viper.SetDefault("key_type", "rsa") viper.SetDefault("key_size", 2048) viper.SetDefault("validity", "24h") + viper.SetDefault("validateTLSCertificate", true) } func readConfig(path string) (*config, error) { diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go index 2bac63a..564664c 100644 --- a/cmd/cashier/main.go +++ b/cmd/cashier/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/tls" "encoding/json" "flag" "fmt" @@ -37,7 +38,11 @@ func installCert(a agent.Agent, cert *ssh.Certificate, key key) error { return nil } -func send(s []byte, token, ca string) (*lib.SignResponse, error) { +func send(s []byte, token, ca string, ValidateTLSCertificate bool) (*lib.SignResponse, error) { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: !ValidateTLSCertificate}, + } + client := &http.Client{Transport: transport} req, err := http.NewRequest("POST", ca+"/sign", bytes.NewReader(s)) if err != nil { return nil, err @@ -45,7 +50,6 @@ func send(s []byte, token, ca string) (*lib.SignResponse, error) { req.Header.Set("Content-Type", "application/json") req.Header.Add("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err @@ -79,7 +83,7 @@ func sign(pub ssh.PublicKey, token string, conf *config) (*ssh.Certificate, erro if err != nil { return nil, err } - resp, err := send(s, token, conf.CA) + resp, err := send(s, token, conf.CA, conf.ValidateTLSCertificate) if err != nil { return nil, err } -- cgit v1.2.3 From acb41bd824cc9dec47a9b28d87bc2adc8e39a21c Mon Sep 17 00:00:00 2001 From: Patrick O'Doherty Date: Sat, 28 May 2016 21:28:01 +0100 Subject: fix client_test.go invocations of send method --- cmd/cashier/client_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cashier/client_test.go b/cmd/cashier/client_test.go index 426128c..f0176c6 100644 --- a/cmd/cashier/client_test.go +++ b/cmd/cashier/client_test.go @@ -58,7 +58,7 @@ func TestSignGood(t *testing.T) { fmt.Fprintln(w, string(j)) })) defer ts.Close() - _, err := send([]byte(`{}`), "token", ts.URL) + _, err := send([]byte(`{}`), "token", ts.URL, true) if err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func TestSignBad(t *testing.T) { fmt.Fprintln(w, string(j)) })) defer ts.Close() - _, err := send([]byte(`{}`), "token", ts.URL) + _, err := send([]byte(`{}`), "token", ts.URL, true) if err != nil { t.Fatal(err) } -- cgit v1.2.3 From 4dfb29f2e07fe6fb90544ba66cf0a3df187072d9 Mon Sep 17 00:00:00 2001 From: Niall Sheridan Date: Sat, 28 May 2016 22:30:42 +0100 Subject: Use flags as defaults, allow them to override config file --- README.md | 2 +- cmd/cashier/config.go | 9 +++++---- cmd/cashier/main.go | 13 +++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0eab987..e35cdf0 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The user can now ssh to the production machine, and continue to ssh to any machi # Usage Cashier comes in two parts, a [cli](cmd/cashier) and a [server](cmd/cashierd). -The client is configured using a [HCL](https://github.com/hashicorp/hcl) configuration file - [example](example-client.cfg). +The client is configured using either a [HCL](https://github.com/hashicorp/hcl) configuration file - [example](example-client.cfg) - or command-line flags. The server is configured using a JSON configuration file - [example](example-server.json). For the server you need the following: diff --git a/cmd/cashier/config.go b/cmd/cashier/config.go index 1196cbd..eed98e1 100644 --- a/cmd/cashier/config.go +++ b/cmd/cashier/config.go @@ -1,6 +1,7 @@ package main import ( + "github.com/spf13/pflag" "github.com/spf13/viper" ) @@ -13,10 +14,10 @@ type config struct { } func setDefaults() { - viper.SetDefault("ca", "http://localhost:10000") - viper.SetDefault("key_type", "rsa") - viper.SetDefault("key_size", 2048) - viper.SetDefault("validity", "24h") + viper.BindPFlag("ca", pflag.Lookup("ca")) + viper.BindPFlag("key_type", pflag.Lookup("key_type")) + viper.BindPFlag("key_size", pflag.Lookup("key_size")) + viper.BindPFlag("validity", pflag.Lookup("validity")) viper.SetDefault("validateTLSCertificate", true) } diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go index 564664c..768ebcd 100644 --- a/cmd/cashier/main.go +++ b/cmd/cashier/main.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/tls" "encoding/json" - "flag" "fmt" "io/ioutil" "log" @@ -17,13 +16,18 @@ import ( "github.com/nsheridan/cashier/lib" "github.com/pkg/browser" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) var ( - u, _ = user.Current() - cfg = flag.String("config", path.Join(u.HomeDir, ".cashier.cfg"), "Path to config file") + 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") ) func installCert(a agent.Agent, cert *ssh.Certificate, key key) error { @@ -102,7 +106,8 @@ func sign(pub ssh.PublicKey, token string, conf *config) (*ssh.Certificate, erro } func main() { - flag.Parse() + pflag.Parse() + c, err := readConfig(*cfg) if err != nil { log.Fatalf("Error parsing config file: %v\n", err) -- cgit v1.2.3