Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Allow krb5 config through environment variables #157

Merged
merged 5 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.7.0

### Changed

* krb5 authenticator supports standard Kerberos environment variables for configuration

## 1.6.0

### Changed
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ The package supports authentication via 3 methods.
### Kerberos Parameters

* `authenticator` - set this to `krb5` to enable kerberos authentication. If this is not present, the default provider would be `ntlm` for unix and `winsspi` for windows.
* `krb5-configfile` (mandatory) - path to kerberos configuration file.
* `krb5-realm` (required with keytab and raw credentials) - Domain name for kerberos authentication.
* `krb5-keytabfile` - path to Keytab file.
* `krb5-credcachefile` - path to Credential cache.
* `krb5-configfile` (optional) - path to kerberos configuration file. Defaults to `/etc/krb5.conf`. Can also be set using `KRB5_CONFIG` environment variable.
* `krb5-realm` (required with keytab and raw credentials) - Domain name for kerberos authentication. Omit this parameter if the realm is part of the user name like `username@REALM`.
* `krb5-keytabfile` - path to Keytab file. Can also be set using environment variable `KRB5_KTNAME`. If no parameter or environment variable is set, the `DefaultClientKeytabName` value from the krb5 config file is used.
* `krb5-credcachefile` - path to Credential cache. Can also be set using environment variable `KRBCCNAME`.
* `krb5-dnslookupkdc` - Optional parameter in all contexts. Set to lookup KDCs in DNS. Boolean. Default is true.
* `krb5-udppreferencelimit` - Optional parameter in all contexts. 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700. Integer. Default is 1.

Expand Down
101 changes: 78 additions & 23 deletions integratedauth/krb5/krb5.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package krb5
import (
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"strconv"
Expand Down Expand Up @@ -44,9 +43,10 @@ var (
)

var (
_ integratedauth.IntegratedAuthenticator = (*krbAuth)(nil)
fileExists = fileExistsOS
AuthProviderFunc integratedauth.Provider = integratedauth.ProviderFunc(getAuth)
_ integratedauth.IntegratedAuthenticator = (*krbAuth)(nil)
fileExists = fileExistsOS
loadDefaultConfigFromFile = newKrb5ConfigFromFile
AuthProviderFunc integratedauth.Provider = integratedauth.ProviderFunc(getAuth)
)

func init() {
Expand Down Expand Up @@ -95,40 +95,95 @@ type krb5Login struct {
}

// copies string parameters from connection string, parses optional parameters
func readKrb5Config(config msdsn.Config) (*krb5Login, error) {
// Environment variables for Kerberos config are listed at https://web.mit.edu/kerberos/krb5-1.12/doc/admin/env_variables.html
func readKrb5Config(cfg msdsn.Config) (l *krb5Login, err error) {
login := &krb5Login{
Krb5ConfigFile: config.Parameters[keytabConfigFile],
KeytabFile: config.Parameters[keytabFile],
CredCacheFile: config.Parameters[credCacheFile],
Realm: config.Parameters[realm],
UserName: config.User,
Password: config.Password,
ServerSPN: config.ServerSPN,
Krb5ConfigFile: cfg.Parameters[keytabConfigFile],
KeytabFile: cfg.Parameters[keytabFile],
CredCacheFile: cfg.Parameters[credCacheFile],
Realm: cfg.Parameters[realm],
UserName: cfg.User,
Password: cfg.Password,
ServerSPN: cfg.ServerSPN,
DNSLookupKDC: true,
UDPPreferenceLimit: 1,
loginMethod: none,
}

// If no conf file is provided , use the environment variable first then just use the default conf file location if not set
if len(login.Krb5ConfigFile) == 0 {
login.Krb5ConfigFile = os.Getenv("KRB5_CONFIG")
}

if len(login.Krb5ConfigFile) == 0 {
login.Krb5ConfigFile = `/etc/krb5.conf`
}

defaults, cerr := loadDefaultConfigFromFile(login)
shueybubbles marked this conversation as resolved.
Show resolved Hide resolved
if cerr != nil {
err = fmt.Errorf("Unable to load krb5 config to get default values: %w", cerr)
return
}

// If no Realm passed, try to split out the user name as `username@realm`
shueybubbles marked this conversation as resolved.
Show resolved Hide resolved
if len(login.Realm) == 0 {
nameParts := strings.SplitN(login.UserName, "@", 2)
if len(nameParts) > 1 {
login.UserName = nameParts[0]
login.Realm = nameParts[1]
}
}

if len(login.Realm) == 0 {
login.Realm = defaults.LibDefaults.DefaultRealm
}

// If the app provides a user name with no password, give the keytab file precedence over the credential cache
if len(login.UserName) > 0 && len(login.Password) == 0 {
if len(login.KeytabFile) == 0 {
login.KeytabFile = os.Getenv("KRB5_KTNAME")
}
if len(login.KeytabFile) == 0 {
kt := defaults.LibDefaults.DefaultClientKeytabName
if ok, _ := fileExists(kt, nil); ok {
login.KeytabFile = kt
}
}
if len(login.KeytabFile) == 0 {
kt := defaults.LibDefaults.DefaultKeytabName
if ok, _ := fileExists(kt, nil); ok {
login.KeytabFile = kt
}
}
}

// We fall back to the environment variable if set, but it will be ignored for login if login.KeytabFile is set
if len(login.CredCacheFile) == 0 {
login.CredCacheFile = os.Getenv("KRB5CCNAME")
}

// read optional parameters
val, ok := config.Parameters[dnsLookupKDC]
val, ok := cfg.Parameters[dnsLookupKDC]
if ok {
parsed, err := strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf("invalid '%s' parameter '%s': %s", dnsLookupKDC, val, err.Error())
parsed, perr := strconv.ParseBool(val)
if perr != nil {
err = fmt.Errorf("invalid '%s' parameter '%s': %s", dnsLookupKDC, val, perr.Error())
return
}
login.DNSLookupKDC = parsed
}

val, ok = config.Parameters[udpPreferenceLimit]
val, ok = cfg.Parameters[udpPreferenceLimit]
if ok {
parsed, err := strconv.Atoi(val)
if err != nil {
return nil, fmt.Errorf("invalid '%s' parameter '%s': %s", udpPreferenceLimit, val, err.Error())
parsed, serr := strconv.Atoi(val)
if serr != nil {
err = fmt.Errorf("invalid '%s' parameter '%s': %s", udpPreferenceLimit, val, serr.Error())
return
}
login.UDPPreferenceLimit = parsed
}

return login, nil
l = login
return
}

func validateKrb5LoginParams(krbLoginParams *krb5Login) error {
Expand Down Expand Up @@ -292,7 +347,7 @@ func clientFromUsernameAndPassword(krb5Login *krb5Login, cfg *config.Config) (*c

// loads keytab file specified in keytabFile and creates a client from its content, username and realm
func clientFromKeytab(krb5Login *krb5Login, cfg *config.Config) (*client.Client, error) {
data, err := ioutil.ReadFile(krb5Login.KeytabFile)
data, err := os.ReadFile(krb5Login.KeytabFile)
if err != nil {
return nil, err
}
Expand Down