Merge pull request #380 from kgutwin/cert-cli
Enable TLS client cert authentication via the CLI
This commit is contained in:
commit
3c58773598
47
builtin/credential/cert/cli.go
Normal file
47
builtin/credential/cert/cli.go
Normal 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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue