Merge pull request #380 from kgutwin/cert-cli

Enable TLS client cert authentication via the CLI
This commit is contained in:
Armon Dadgar 2015-06-30 11:44:28 -07:00
commit 3c58773598
7 changed files with 105 additions and 6 deletions

View file

@ -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)
}

View file

@ -46,7 +46,7 @@ func (b *backend) pathLogin(
// If no trusted chain was found, client is not authenticated // If no trusted chain was found, client is not authenticated
if len(trustedChains) == 0 { 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 // Match the trusted chain with the policy

View file

@ -91,6 +91,7 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
"github": &credGitHub.CLIHandler{}, "github": &credGitHub.CLIHandler{},
"userpass": &credUserpass.CLIHandler{}, "userpass": &credUserpass.CLIHandler{},
"ldap": &credLdap.CLIHandler{}, "ldap": &credLdap.CLIHandler{},
"cert": &credCert.CLIHandler{},
}, },
}, nil }, nil
}, },

View file

@ -270,6 +270,13 @@ General Options:
to verify the Vault server SSL certificate. If both to verify the Vault server SSL certificate. If both
-ca-cert and -ca-path are specified, -ca-path is used. -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 -tls-skip-verify Do not verify TLS certificate. This is highly
not recommended. not recommended.

View file

@ -25,6 +25,8 @@ import (
const EnvVaultAddress = "VAULT_ADDR" const EnvVaultAddress = "VAULT_ADDR"
const EnvVaultCACert = "VAULT_CACERT" const EnvVaultCACert = "VAULT_CACERT"
const EnvVaultCAPath = "VAULT_CAPATH" const EnvVaultCAPath = "VAULT_CAPATH"
const EnvVaultClientCert = "VAULT_CLIENT_CERT"
const EnvVaultClientKey = "VAULT_CLIENT_KEY"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY" const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
// FlagSetFlags is an enum to define what flags are present in the // 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 ForceConfig *Config // Force a config, don't load from disk
// These are set by the command line flags. // These are set by the command line flags.
flagAddress string flagAddress string
flagCACert string flagCACert string
flagCAPath string flagCAPath string
flagInsecure bool flagClientCert string
flagClientKey string
flagInsecure bool
// These are internal and shouldn't be modified or access by anyone // These are internal and shouldn't be modified or access by anyone
// except Meta. // except Meta.
@ -77,6 +81,12 @@ func (m *Meta) Client() (*api.Client, error) {
if v := os.Getenv(EnvVaultCAPath); v != "" { if v := os.Getenv(EnvVaultCAPath); v != "" {
m.flagCAPath = 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 != "" { if v := os.Getenv(EnvVaultInsecure); v != "" {
var err error var err error
m.flagInsecure, err = strconv.ParseBool(v) m.flagInsecure, err = strconv.ParseBool(v)
@ -103,6 +113,16 @@ func (m *Meta) Client() (*api.Client, error) {
RootCAs: certPool, RootCAs: certPool,
} }
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 client := *http.DefaultClient
client.Transport = &http.Transport{ client.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
@ -184,6 +204,8 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
f.StringVar(&m.flagAddress, "address", "", "") f.StringVar(&m.flagAddress, "address", "", "")
f.StringVar(&m.flagCACert, "ca-cert", "", "") f.StringVar(&m.flagCACert, "ca-cert", "", "")
f.StringVar(&m.flagCAPath, "ca-path", "", "") 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, "insecure", false, "")
f.BoolVar(&m.flagInsecure, "tls-skip-verify", false, "") f.BoolVar(&m.flagInsecure, "tls-skip-verify", false, "")
} }

View file

@ -19,7 +19,7 @@ func TestFlagSet(t *testing.T) {
}, },
{ {
FlagSetServer, 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) { func TestEnvSettings(t *testing.T) {
os.Setenv("VAULT_CACERT", "/path/to/fake/cert.crt") os.Setenv("VAULT_CACERT", "/path/to/fake/cert.crt")
os.Setenv("VAULT_CAPATH", "/path/to/fake/certs") 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") os.Setenv("VAULT_SKIP_VERIFY", "true")
defer os.Setenv("VAULT_CACERT", "") defer os.Setenv("VAULT_CACERT", "")
defer os.Setenv("VAULT_CAPATH", "") defer os.Setenv("VAULT_CAPATH", "")
defer os.Setenv("VAULT_CLIENT_CERT", "")
defer os.Setenv("VAULT_CLIENT_KEY", "")
defer os.Setenv("VAULT_SKIP_VERIFY", "") defer os.Setenv("VAULT_SKIP_VERIFY", "")
var m Meta var m Meta
@ -60,6 +64,12 @@ func TestEnvSettings(t *testing.T) {
if m.flagCAPath != "/path/to/fake/certs" { if m.flagCAPath != "/path/to/fake/certs" {
t.Fatalf("bad: %s", m.flagAddress) 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 { if m.flagInsecure != true {
t.Fatalf("bad: %s", m.flagAddress) t.Fatalf("bad: %s", m.flagAddress)
} }

View file

@ -19,10 +19,22 @@ from an external source.
## Authentication ## 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 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 certificate and when the login endpoint is hit, the auth backend will determine
if there is a matching trusted certificate to authenticate the client. 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 ## Configuration
First, you must enable the certificate auth backend: First, you must enable the certificate auth backend: