Skip to content

Commit

Permalink
# This is a combination of 2 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

# This is a combination of 2 commits.
# This is the 1st commit message:

# This is a combination of 2 commits.
# This is the 1st commit message:

# This is a combination of 2 commits.
# This is the 1st commit message:

# This is a combination of 13 commits.
# This is the 1st commit message:

support for DNS srv records and retry features for other KDCs

# This is the commit message #2:

filter out unsupported etypes in krb5.conf

# This is the commit message #3:

env var for ip

# This is the commit message #4:

update entrypoint

# This is the commit message #5:

intg test env

# This is the commit message #6:

dns entries

# This is the commit message #7:

travis nameserver

# This is the commit message #8:

dns intg tests

# This is the commit message #9:

fix network infinite loop

# This is the commit message #10:

fix issue

# This is the commit message #11:

travis env var

# This is the commit message #12:

missing port

# This is the commit message #13:

rebase

# This is the commit message #2:

update entrypoint

# This is the commit message #2:

dns entries

# This is the commit message #2:

fix network infinite loop

# This is the commit message #2:

missing port
  • Loading branch information
jcmturner committed Nov 30, 2017
1 parent f43b73e commit b762b59
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 61 deletions.
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ services:

before_install:
- docker pull jcmturner/gokrb5:http
- docker pull jcmturner/gokrb5:dns
- docker pull jcmturner/gokrb5:kdc-centos-default
- docker pull jcmturner/gokrb5:kdc-older
- docker pull jcmturner/gokrb5:kdc-latest
- docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -e "TEST_KDC_ADDR=127.0.0.1" -p 53:53 -p 53:53/udp --name dns jcmturner/gokrb5:dns
- docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 88:88 -p 88:88/udp --name krb5kdc jcmturner/gokrb5:kdc-centos-default
- docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 78:88 -p 78:88/udp --name krb5kdc-old jcmturner/gokrb5:kdc-older
- docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 98:88 -p 98:88/udp --name krb5kdc-latest jcmturner/gokrb5:kdc-latest
- docker run -d --add-host host.test.gokrb5:127.0.0.88 -v /etc/localtime:/etc/localtime:ro -p 80:80 -p 443:443 --name gokrb5-http jcmturner/gokrb5:http

before_stript:
- cat /etc/resolv.conf

env:
- TEST_KDC_ADDR=127.0.0.1 TEST_HTTP_URL="http://host.test.gokrb5/index.html"
- TEST_KDC_ADDR=127.0.0.1 TEST_HTTP_URL="http://host.test.gokrb5/index.html" DNSUTILS_OVERRIDE_NS="127.0.0.1:53"

addons:
hosts:
- host.test.gokrb5
- kdc.test.gokrb5

14 changes: 8 additions & 6 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,17 @@ func (cl *Client) IsConfigured() (bool, error) {
if cl.Config.LibDefaults.DefaultRealm == "" {
return false, errors.New("client krb5 config does not have a default realm")
}
for _, r := range cl.Config.Realms {
if r.Realm == cl.Config.LibDefaults.DefaultRealm {
if len(r.KDC) > 0 {
return true, nil
if !cl.Config.LibDefaults.DNSLookupKDC {
for _, r := range cl.Config.Realms {
if r.Realm == cl.Config.LibDefaults.DefaultRealm {
if len(r.KDC) > 0 {
return true, nil
}
return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
}
return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
}
}
return false, errors.New("client does not have KDCs configured for the default realm")
return true, nil
}

// Login the client with the KDC via an AS exchange.
Expand Down
64 changes: 64 additions & 0 deletions client/client_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,67 @@ func TestNewClientFromCCache(t *testing.T) {
t.Fatalf("Client was not configured from CCache: %v", err)
}
}

// Only works for Go 1.9 creates false negativce for Go <= 1.8
//func TestResolveKDC(t *testing.T) {
// c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
// ns := os.Getenv("DNSUTILS_OVERRIDE_NS")
// if ns == "" {
// os.Setenv("DNSUTILS_OVERRIDE_NS", testdata.TEST_NS)
// }
// c.LibDefaults.DNSLookupKDC = true
// var cl Client
// cl.WithConfig(c)
// count, res, err := cl.resolveKDC(c.LibDefaults.DefaultRealm, true)
// if err != nil {
// t.Errorf("error resolving KDC via DNS TCP: %v", err)
// }
// assert.Equal(t, 5, count, "Number of SRV records not as expected")
// assert.Equal(t, count, len(res), "Map size does not match")
// expected := []string{
// "kdc.test.gokrb5:88",
// "kdc1a.test.gokrb5:88",
// "kdc2a.test.gokrb5:88",
// "kdc1b.test.gokrb5:88",
// "kdc2b.test.gokrb5:88",
// }
// for _, s := range expected {
// var found bool
// for _, v := range res {
// if s == v {
// found = true
// break
// }
// }
// assert.True(t, found, "Record %s not found in results", s)
// }
// c.LibDefaults.DNSLookupKDC = false
// count, res, err = cl.resolveKDC(c.LibDefaults.DefaultRealm, true)
// if err != nil {
// t.Errorf("error resolving KDCs from config: %v", err)
// }
// assert.Equal(t, "127.0.0.1:88", res[1], "KDC not read from config as expected")
//}
//
//func TestClient_Login_DNSKDCs(t *testing.T) {
// ns := os.Getenv("DNSUTILS_OVERRIDE_NS")
// if ns == "" {
// os.Setenv("DNSUTILS_OVERRIDE_NS", testdata.TEST_NS)
// }
//
// c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
// // Set to lookup KDCs in DNS
// c.LibDefaults.DNSLookupKDC = true
// //Blank out the KDCs to ensure they are not being used
// c.Realms = []config.Realm{}
//
// b, err := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
// kt, _ := keytab.Parse(b)
// cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
// cl.WithConfig(c)
//
// err = cl.Login()
// if err != nil {
// t.Errorf("Error on logging in using DNS lookup of KDCs: %v\n", err)
// }
//}
180 changes: 127 additions & 53 deletions client/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,137 +3,209 @@ package client
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"gopkg.in/jcmturner/dnsutils.v1"
"io"
"gopkg.in/jcmturner/gokrb5.v2/iana/errorcode"
"gopkg.in/jcmturner/gokrb5.v2/messages"
"math/rand"
"net"
"strconv"
"strings"
"time"

"gopkg.in/jcmturner/gokrb5.v2/iana/errorcode"
"gopkg.in/jcmturner/gokrb5.v2/messages"
)

// SendToKDC performs network actions to send data to the KDC.
func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
var rb []byte
var kdcs []string
for _, r := range cl.Config.Realms {
if r.Realm == realm {
kdcs = r.KDC
break
func (cl *Client) resolveKDC(realm string, tcp bool) (int, map[int]string, error) {
kdcs := make(map[int]string)
var count int

// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
if cl.Config.LibDefaults.DNSLookupKDC {
proto := "udp"
if tcp {
proto = "tcp"
}
c, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
if err != nil {
return count, kdcs, err
}
if len(addrs) < 1 {
return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
}
count = c
for k, v := range addrs {
kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
}
}
if len(kdcs) < 1 {
return rb, fmt.Errorf("No KDCs defined in configuration for realm: %v", cl.Config.LibDefaults.DefaultRealm)
}
var kdc string
if len(kdcs) > 1 {
//Select one of the KDCs at random
kdc = kdcs[rand.Intn(len(kdcs))]
} else {
kdc = kdcs[0]
// Get the KDCs from the krb5.conf an order them randomly for preference.
var ks []string
for _, r := range cl.Config.Realms {
if r.Realm == realm {
ks = r.KDC
break
}
}
count = len(ks)
if count < 1 {
return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
}
i := 1
if count > 1 {
l := len(ks)
for l > 0 {
ri := rand.Intn(l)
kdcs[i] = ks[ri]
if l > 1 {
// Remove the entry from the source slice by swapping with the last entry and truncating
ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
ks = ks[:len(ks)-1]
l = len(ks)
} else {
l = 0
}
i += 1
}
} else {
kdcs[i] = ks[0]
}
}
return count, kdcs, nil
}

// SendToKDC performs network actions to send data to the KDC.
func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
var rb []byte
if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
//1 means we should always use TCP
rb, errtcp := sendTCP(kdc, b)
rb, errtcp := cl.sendTCP(realm, b)
if errtcp != nil {
if e, ok := errtcp.(messages.KRBError); ok {
return rb, e
}
return rb, fmt.Errorf("Failed to communicate with KDC %v via TCP (%v)", kdc, errtcp)
}
if len(rb) < 1 {
return rb, fmt.Errorf("No response data from KDC %v", kdc)
return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
}
return rb, nil
}
if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
//Try UDP first, TCP second
rb, errudp := sendUDP(kdc, b)
rb, errudp := cl.sendUDP(realm, b)
if errudp != nil {
if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
// Got a KRBError from KDC
// If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
return rb, e
}
// Try TCP
r, errtcp := sendTCP(kdc, b)
r, errtcp := cl.sendTCP(realm, b)
if errtcp != nil {
if e, ok := errtcp.(messages.KRBError); ok {
// Got a KRBError
return r, e
}
return r, fmt.Errorf("Failed to communicate with KDC %v. Attempts made with UDP (%v) and then TCP (%v)", kdc, errudp, errtcp)
return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
}
rb = r
}
if len(rb) < 1 {
return rb, fmt.Errorf("No response data from KDC %v", kdc)
}
return rb, nil
}
//Try TCP first, UDP second
rb, errtcp := sendTCP(kdc, b)
rb, errtcp := cl.sendTCP(realm, b)
if errtcp != nil {
if e, ok := errtcp.(messages.KRBError); ok {
// Got a KRBError from KDC so returning and not trying UDP.
return rb, e
}
rb, errudp := sendUDP(kdc, b)
rb, errudp := cl.sendUDP(realm, b)
if errudp != nil {
if e, ok := errudp.(messages.KRBError); ok {
// Got a KRBError
return rb, e
}
return rb, fmt.Errorf("Failed to communicate with KDC %v. Attempts made with TCP (%v) and then UDP (%v)", kdc, errtcp, errudp)
return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
}
}
if len(rb) < 1 {
return rb, fmt.Errorf("No response data from KDC %v", kdc)
}
return rb, nil
}

func dialKDCUDP(count int, kdcs map[int]string) (conn *net.UDPConn, err error) {
i := 1
for i <= count {
udpAddr, e := net.ResolveUDPAddr("udp", kdcs[i])
if e != nil {
err = fmt.Errorf("error resolving KDC address: %v", e)
return
}
conn, err = net.DialUDP("udp", nil, udpAddr)
if err == nil {
conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
return
}
i += 1
}
err = errors.New("error in getting a UDP connection to any of the KDCs")
return
}

func dialKDCTCP(count int, kdcs map[int]string) (conn *net.TCPConn, err error) {
i := 1
for i <= count {
tcpAddr, e := net.ResolveTCPAddr("tcp", kdcs[i])
if e != nil {
err = fmt.Errorf("error resolving KDC address: %v", e)
return
}
conn, err = net.DialTCP("tcp", nil, tcpAddr)
if err == nil {
conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
return
}
i += 1
}
err = errors.New("error in getting a TCP connection to any of the KDCs")
return
}

// Send the bytes to the KDC over UDP.
func sendUDP(kdc string, b []byte) ([]byte, error) {
func (cl *Client) sendUDP(realm string, b []byte) ([]byte, error) {
var r []byte
udpAddr, err := net.ResolveUDPAddr("udp", kdc)
count, kdcs, err := cl.resolveKDC(realm, false)
if err != nil {
return r, fmt.Errorf("Error resolving KDC address: %v", err)
return r, err
}
conn, err := net.DialUDP("udp", nil, udpAddr)
conn, err := dialKDCUDP(count, kdcs)
if err != nil {
return r, fmt.Errorf("Error establishing connection to KDC: %v", err)
return r, err
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))
_, err = conn.Write(b)
if err != nil {
return r, fmt.Errorf("Error sending to KDC: %v", err)
return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
}
udpbuf := make([]byte, 4096)
n, _, err := conn.ReadFrom(udpbuf)
r = udpbuf[:n]
if err != nil {
return r, fmt.Errorf("Sending over UDP failed: %v", err)
return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
}
if len(r) < 1 {
return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
}
return checkForKRBError(r)
}

// Send the bytes to the KDC over TCP.
func sendTCP(kdc string, b []byte) ([]byte, error) {
func (cl *Client) sendTCP(realm string, b []byte) ([]byte, error) {
var r []byte
tcpAddr, err := net.ResolveTCPAddr("tcp", kdc)
count, kdcs, err := cl.resolveKDC(realm, true)
if err != nil {
return r, fmt.Errorf("Error resolving KDC address: %v", err)
return r, err
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
conn, err := dialKDCTCP(count, kdcs)
if err != nil {
return r, fmt.Errorf("Error establishing connection to KDC: %v", err)
return r, err
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second)))

/*
RFC https://tools.ietf.org/html/rfc4120#section-7.2.2
Expand All @@ -153,7 +225,7 @@ func sendTCP(kdc string, b []byte) ([]byte, error) {

_, err = conn.Write(b)
if err != nil {
return r, fmt.Errorf("Error sending to KDC: %v", err)
return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
}

sh := make([]byte, 4, 4)
Expand All @@ -168,7 +240,9 @@ func sendTCP(kdc string, b []byte) ([]byte, error) {
if err != nil {
return r, fmt.Errorf("error reading response: %v", err)
}

if len(rb) < 1 {
return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
}
return checkForKRBError(rb)
}

Expand Down
Loading

0 comments on commit b762b59

Please sign in to comment.