open-vault/vendor/github.com/hashicorp/vault-plugin-auth-kerberos/cli.go
Jim Kalafut b7fc72d5ec
Update go.mod and vendoring (#8752)
This primarily ports updates made during the 1.4 release to master.
2020-04-16 12:07:07 -07:00

179 lines
5.2 KiB
Go

package kerberos
import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/api"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/spnego"
)
// CLIHandler fulfills Vault's LoginHandler interface.
type CLIHandler struct{}
// Auth takes a client and a config map, and returns a secret if appropriate.
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
mount, ok := m["mount"]
if !ok {
mount = "kerberos"
}
username := m["username"]
if username == "" {
return nil, errors.New(`"username" is required`)
}
service := m["service"]
if service == "" {
return nil, errors.New(`"service" is required`)
}
realm := m["realm"]
if realm == "" {
return nil, errors.New(`"realm" is required`)
}
keytabPath := m["keytab_path"]
if keytabPath == "" {
return nil, errors.New(`"keytab_path" is required`)
}
krb5ConfPath := m["krb5conf_path"]
if krb5ConfPath == "" {
return nil, errors.New(`"krb5conf_path" is required`)
}
disableFAST := false
disableFASTNegotiation := m["disable_fast_negotiation"]
if disableFASTNegotiation != "" {
setting, err := strconv.ParseBool(disableFASTNegotiation)
if err != nil {
return nil, fmt.Errorf(`invalid value "%s" for disable_fast_negotiation, must be "true" or "false"`, disableFASTNegotiation)
}
disableFAST = setting
}
loginCfg := &LoginCfg{
Username: username,
Service: service,
Realm: realm,
KeytabPath: keytabPath,
Krb5ConfPath: krb5ConfPath,
DisableFASTNegotiation: disableFAST,
}
authHeaderVal, err := GetAuthHeaderVal(loginCfg)
if err != nil {
return nil, err
}
headers := http.Header{}
headers.Set(spnego.HTTPHeaderAuthRequest, authHeaderVal)
c.SetHeaders(headers)
path := fmt.Sprintf("auth/%s/login", mount)
secret, err := c.Logical().Write(path, nil)
if err != nil {
return nil, err
}
if secret == nil {
return nil, errors.New("empty response from credential provider")
}
return secret, nil
}
func (h *CLIHandler) Help() string {
help := `
Usage: vault login -method=kerberos [CONFIG K=V...]
The Kerberos auth method allows users to authenticate using Kerberos
combined with LDAP.
Example authentication:
$ vault login -method=kerberos \
-username=grace \
-service="HTTP/ab10dfy3be7v.matrix.lan:8200" \
-realm=MATRIX.LAN \
-keytab_path=/etc/krb5/krb5.keytab \
-krb5conf_path=/etc/krb5.conf
Configuration:
krb5conf_path=<string>
The path to a valid krb5.conf file describing how to communicate with the Kerberos environment.
keytab_path=<string>
The path to the keytab in which the entry lives for the entity authenticating to Vault.
username=<string>
The username for the entry _within_ the keytab to use for logging into Kerberos.
service=<string>
The service principal name to use in obtaining a service ticket for gaining a SPNEGO token.
realm=<string>
The name of the Kerberos realm.
`
return strings.TrimSpace(help)
}
// LoginCfg is a struct with explicitly-named string fields to prevent
// bugs related to incorrectly ordering the strings being passed into
// GetAuthHeaderVal.
type LoginCfg struct {
Username, Service, Realm, KeytabPath, Krb5ConfPath string
// FAST is a pre-authentication framework for Kerberos. It includes
// a mechanism for tunneling pre-authentication exchanges using armoured
// KDC messages. FAST provides increased resistance to passive password
// guessing attacks.
// Some common Kerberos implementations do not support FAST negotiation.
DisableFASTNegotiation bool
}
// GetAuthHeaderVal is a convenience function that takes a given loginCfg
// and returns the value for the "Authorization" header that should be
// provided to Vault for a successful SPNEGO login.
func GetAuthHeaderVal(loginCfg *LoginCfg) (string, error) {
kt, err := keytab.Load(loginCfg.KeytabPath)
if err != nil {
return "", errwrap.Wrapf("couldn't load keytab: {{err}}", err)
}
krb5Conf, err := config.Load(loginCfg.Krb5ConfPath)
if err != nil {
return "", errwrap.Wrapf("couldn't parse krb5Conf: {{err}}", err)
}
settings := []func(*client.Settings){
client.AssumePreAuthentication(true),
}
if loginCfg.DisableFASTNegotiation {
settings = append(settings, client.DisablePAFXFAST(true))
}
cl := client.NewWithKeytab(loginCfg.Username, loginCfg.Realm, kt, krb5Conf, settings...)
if err := cl.Login(); err != nil {
return "", errwrap.Wrapf("couldn't log in: {{err}}", err)
}
defer cl.Destroy()
spnegoClient := spnego.SPNEGOClient(cl, loginCfg.Service)
if err := spnegoClient.AcquireCred(); err != nil {
return "", errwrap.Wrapf("couldn't acquire client credential: {{err}}", err)
}
spnegoToken, err := spnegoClient.InitSecContext()
if err != nil {
return "", errwrap.Wrapf("couldn't initialize context: {{err}}", err)
}
marshalledToken, err := spnegoToken.Marshal()
if err != nil {
return "", errwrap.Wrapf("couldn't marshal SPNEGO: {{err}}", err)
}
authHeaderVal := "Negotiate " + base64.StdEncoding.EncodeToString(marshalledToken)
return authHeaderVal, nil
}