From 24d0af39b4ecb22cf311d039c58d1d966a31edb8 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Mon, 29 Jun 2015 15:33:16 -0400 Subject: [PATCH 1/5] Initial sketch for client TLS auth --- command/meta.go | 28 ++++++++++++++++++++++++---- command/meta_test.go | 12 +++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/command/meta.go b/command/meta.go index 96f53f1f5..6b7e1c602 100644 --- a/command/meta.go +++ b/command/meta.go @@ -25,6 +25,8 @@ import ( const EnvVaultAddress = "VAULT_ADDR" const EnvVaultCACert = "VAULT_CACERT" const EnvVaultCAPath = "VAULT_CAPATH" +const EnvVaultClientCert = "VAULT_CLIENT_CERT" +const EnvVaultClientKey = "VAULT_CLIENT_KEY" const EnvVaultInsecure = "VAULT_SKIP_VERIFY" // FlagSetFlags is an enum to define what flags are present in the @@ -48,10 +50,12 @@ type Meta struct { ForceConfig *Config // Force a config, don't load from disk // These are set by the command line flags. - flagAddress string - flagCACert string - flagCAPath string - flagInsecure bool + flagAddress string + flagCACert string + flagCAPath string + flagClientCert string + flagClientKey string + flagInsecure bool // These are internal and shouldn't be modified or access by anyone // except Meta. @@ -77,6 +81,12 @@ func (m *Meta) Client() (*api.Client, error) { if v := os.Getenv(EnvVaultCAPath); v != "" { m.flagCAPath = v } + if v := os.Getenv(EnvVaultClientCert); v != "" { + m.flagClientCert = v + } + if v := os.Getenv(EnvVaultClientKey); v != "" { + m.flagClientKey = v + } if v := os.Getenv(EnvVaultInsecure); v != "" { var err error m.flagInsecure, err = strconv.ParseBool(v) @@ -103,6 +113,14 @@ func (m *Meta) Client() (*api.Client, error) { RootCAs: certPool, } + if m.flagClientCert != "" { + tlsCert, err := tls.LoadX509KeyPair(m.flagClientCert, m.flagClientKey) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{tlsCert} + } + client := *http.DefaultClient client.Transport = &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -184,6 +202,8 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet { f.StringVar(&m.flagAddress, "address", "", "") f.StringVar(&m.flagCACert, "ca-cert", "", "") f.StringVar(&m.flagCAPath, "ca-path", "", "") + f.StringVar(&m.flagClientCert, "client-cert", "", "") + f.StringVar(&m.flagClientKey, "client-key", "", "") f.BoolVar(&m.flagInsecure, "insecure", false, "") f.BoolVar(&m.flagInsecure, "tls-skip-verify", false, "") } diff --git a/command/meta_test.go b/command/meta_test.go index e80a41c0a..ee463f32a 100644 --- a/command/meta_test.go +++ b/command/meta_test.go @@ -19,7 +19,7 @@ func TestFlagSet(t *testing.T) { }, { FlagSetServer, - []string{"address", "ca-cert", "ca-path", "insecure", "tls-skip-verify"}, + []string{"address", "ca-cert", "ca-path", "client-cert", "client-key", "insecure", "tls-skip-verify"}, }, } @@ -44,9 +44,13 @@ func TestFlagSet(t *testing.T) { func TestEnvSettings(t *testing.T) { os.Setenv("VAULT_CACERT", "/path/to/fake/cert.crt") os.Setenv("VAULT_CAPATH", "/path/to/fake/certs") + os.Setenv("VAULT_CLIENT_CERT", "/path/to/fake/client.crt") + os.Setenv("VAULT_CLIENT_KEY", "/path/to/fake/client.key") os.Setenv("VAULT_SKIP_VERIFY", "true") defer os.Setenv("VAULT_CACERT", "") defer os.Setenv("VAULT_CAPATH", "") + defer os.Setenv("VAULT_CLIENT_CERT", "") + defer os.Setenv("VAULT_CLIENT_KEY", "") defer os.Setenv("VAULT_SKIP_VERIFY", "") var m Meta @@ -60,6 +64,12 @@ func TestEnvSettings(t *testing.T) { if m.flagCAPath != "/path/to/fake/certs" { t.Fatalf("bad: %s", m.flagAddress) } + if m.flagClientCert != "/path/to/fake/client.crt" { + t.Fatalf("bad: %s", m.flagAddress) + } + if m.flagClientKey != "/path/to/fake/client.key" { + t.Fatalf("bad: %s", m.flagAddress) + } if m.flagInsecure != true { t.Fatalf("bad: %s", m.flagAddress) } From dafcc5b2ceea3af49048f702b2e708ddc2c6b1e9 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Mon, 29 Jun 2015 23:29:41 -0400 Subject: [PATCH 2/5] enable CLI cert login --- builtin/credential/cert/cli.go | 47 ++++++++++++++++++++++++++++++++++ cli/commands.go | 1 + 2 files changed, 48 insertions(+) create mode 100644 builtin/credential/cert/cli.go diff --git a/builtin/credential/cert/cli.go b/builtin/credential/cert/cli.go new file mode 100644 index 000000000..45f353bca --- /dev/null +++ b/builtin/credential/cert/cli.go @@ -0,0 +1,47 @@ +package cert + +import ( + "fmt" + "strings" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/mapstructure" +) + +type CLIHandler struct{} + +func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) { + var data struct { + Mount string `mapstructure:"mount"` + } + if err := mapstructure.WeakDecode(m, &data); err != nil { + return "", err + } + + if data.Mount == "" { + data.Mount = "cert" + } + + path := fmt.Sprintf("auth/%s/login", data.Mount) + secret, err := c.Logical().Write(path, nil) + if err != nil { + return "", err + } + if secret == nil { + return "", fmt.Errorf("empty response from credential provider") + } + + return secret.Auth.ClientToken, nil +} + +func (h *CLIHandler) Help() string { + help := ` +The "cert" credential provider allows you to authenticate with a +client certificate. No other authentication materials are needed. + + Example: vault auth -method=cert + + ` + + return strings.TrimSpace(help) +} diff --git a/cli/commands.go b/cli/commands.go index d115f4a4f..de23a2dc2 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -89,6 +89,7 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { "github": &credGitHub.CLIHandler{}, "userpass": &credUserpass.CLIHandler{}, "ldap": &credLdap.CLIHandler{}, + "cert": &credCert.CLIHandler{}, }, }, nil }, From 0062d923cca1e5eff7e4b62e2003d416656a8842 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Tue, 30 Jun 2015 08:59:38 -0400 Subject: [PATCH 3/5] Better error messages. --- builtin/credential/cert/path_login.go | 2 +- command/meta.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 2b673271e..10ada39cf 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -46,7 +46,7 @@ func (b *backend) pathLogin( // If no trusted chain was found, client is not authenticated if len(trustedChains) == 0 { - return logical.ErrorResponse("invalid certificate"), nil + return logical.ErrorResponse("invalid certificate or no client certificate supplied"), nil } // Match the trusted chain with the policy diff --git a/command/meta.go b/command/meta.go index 6b7e1c602..218ad177e 100644 --- a/command/meta.go +++ b/command/meta.go @@ -113,12 +113,14 @@ func (m *Meta) Client() (*api.Client, error) { RootCAs: certPool, } - if m.flagClientCert != "" { + if m.flagClientCert != "" && m.flagClientKey != "" { tlsCert, err := tls.LoadX509KeyPair(m.flagClientCert, m.flagClientKey) if err != nil { return nil, err } tlsConfig.Certificates = []tls.Certificate{tlsCert} + } else if m.flagClientCert != "" || m.flagClientKey != "" { + return nil, fmt.Errorf("Both client cert and client key must be provided") } client := *http.DefaultClient From c12734b27c16ad794ccf50480055f62141077d95 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Tue, 30 Jun 2015 09:04:57 -0400 Subject: [PATCH 4/5] CLI docs --- command/auth.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/command/auth.go b/command/auth.go index f31b2a358..39265dc93 100644 --- a/command/auth.go +++ b/command/auth.go @@ -270,6 +270,13 @@ General Options: to verify the Vault server SSL certificate. If both -ca-cert and -ca-path are specified, -ca-path is used. + -client-cert=path Path to a PEM encoded client certificate for TLS + authentication to the Vault server. Must also specify + -client-key. + + -client-key=path Path to an unencrypted PEM encoded private key + matching the client certificate from -client-cert. + -tls-skip-verify Do not verify TLS certificate. This is highly not recommended. From 70fc49be847d9ff6aa853e42a8e79b5e719edc85 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Tue, 30 Jun 2015 09:18:39 -0400 Subject: [PATCH 5/5] Website docs. --- website/source/docs/auth/cert.html.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/source/docs/auth/cert.html.md b/website/source/docs/auth/cert.html.md index da978fc60..3fb319fe8 100644 --- a/website/source/docs/auth/cert.html.md +++ b/website/source/docs/auth/cert.html.md @@ -19,10 +19,22 @@ from an external source. ## Authentication +### Via the CLI +``` +vault auth -method=cert \ + -ca-cert=ca.pem -client-cert=cert.pem -client-key=key.pem +``` + +### Via the API The endpoint for the login is `/login`. The client simply connects with their TLS certificate and when the login endpoint is hit, the auth backend will determine if there is a matching trusted certificate to authenticate the client. +``` +curl --cacert ca.pem --cert cert.pem --key key.pem \ + $VAULT_ADDR/v1/auth/cert/login -XPOST +``` + ## Configuration First, you must enable the certificate auth backend: