Skip to content

Commit

Permalink
Version 8
Browse files Browse the repository at this point in the history
- Introduce v8 using Go modules for dependencies
- Client.Key function takes a kvno for which key to return
- Removed stutter on creating client functions
- Removed stutter on creating config functions
- Removed stutter from krberror New func
- Credentials now has method to get AD additional details
- Credentials can now be marshalled/unmarshalled to support sessions
- Store marshalled credentials bytes in http request context
- Support for server side sessions in SPNEGO
- Return kvno from keytab GetEncryptionKey method
- APREQ verify takes point so calling code can access decrypted parts if needs be
  • Loading branch information
jcmturner committed Jan 29, 2020
1 parent 7c25130 commit 3196640
Show file tree
Hide file tree
Showing 318 changed files with 22,983 additions and 474 deletions.
27 changes: 19 additions & 8 deletions .github/workflows/testing.yml
@@ -1,5 +1,11 @@
name: gokrb5
on: [push, pull_request]
name: v7
on:
push:
paths-ignore:
- 'v[0-9]+/**'
pull_request:
paths-ignore:
- 'v[0-9]+/**'

jobs:
build:
Expand Down Expand Up @@ -27,6 +33,8 @@ jobs:

- name: Test well formatted with gofmt
run: |
# Remove major version sub directories
find . -maxdepth 1 -type d -regex '\./v[0-9]+' | xargs -i rm -rf {}
GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/)
test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g')
id: gofmt
Expand All @@ -43,7 +51,9 @@ jobs:
id: goGet

- name: Unit tests
run: go test -race ./...
run: |
cd /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7
go test -race $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/)
id: unitTests

- name: Start integration test dependencies
Expand All @@ -62,23 +72,24 @@ jobs:
id: intgTestDeps

- name: Run Examples
run: |
go run -tags="examples" examples/example.go
run: go run -tags="examples" examples/example.go
id: examples

- name: Tests including integration tests
run: |
go test -race ./...
cd /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7
go test -race $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/)
env:
INTEGRATION: 1
TESTPRIVILEGED: 1
id: intgTests

- name: Tests (32bit)
run: |
go test ./...
cd /home/runner/go/src/gopkg.in/jcmturner/gokrb5.v7
go test $(go list ./... | grep -E -v '/v[0-9]+' | grep -v /vendor/)
env:
GOARCH: 386
INTEGRATION: 1
TESTPRIVILEGED: 1
id: test32
id: test32
85 changes: 85 additions & 0 deletions .github/workflows/testingv8.yml
@@ -0,0 +1,85 @@
# Name of the workflow needs to match the name of the major version directory
name: v8
on:
push:
paths:
- 'v8/**'
pull_request:
paths:
- 'v8/**'

jobs:
build:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go: [ '1.11.x', '1.12.x', '1.13.x' ]
env:
TEST_KDC_ADDR: 127.0.0.1
TEST_HTTP_URL: http://cname.test.gokrb5
TEST_HTTP_ADDR: 127.0.0.1
DNS_IP: 127.0.88.53
DNSUTILS_OVERRIDE_NS: 127.0.88.53:53
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go }}

- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.ref }}

- name: Test well formatted with gofmt
run: |
GO_FILES=$(find ${GITHUB_WORKFLOW} -iname '*.go' -type f | grep -v /vendor/)
test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g')
id: gofmt

- name: Unit tests
run: |
cd ${GITHUB_WORKFLOW}
go test -race ./...
id: unitTests

- name: Start integration test dependencies
run: |
sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq krb5-user
sudo chmod 666 /etc/krb5.conf
sudo docker run -d -h ns.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -e "TEST_KDC_ADDR=${TEST_KDC_ADDR}" -e "TEST_HTTP_ADDR=${TEST_HTTP_ADDR}" -p ${DNSUTILS_OVERRIDE_NS}:53 -p ${DNSUTILS_OVERRIDE_NS}:53/udp --name dns jcmturner/gokrb5:dns
sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 88:88 -p 88:88/udp -p 464:464 -p 464:464/udp --name krb5kdc jcmturner/gokrb5:kdc-centos-default
sudo 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
sudo 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
sudo docker run -d -h kdc.resdom.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 188:88 -p 188:88/udp --name krb5kdc-resdom jcmturner/gokrb5:kdc-resdom
sudo docker run -d -h kdc.test.gokrb5 -v /etc/localtime:/etc/localtime:ro -p 58:88 -p 58:88/udp --name krb5kdc-shorttickets jcmturner/gokrb5:kdc-shorttickets
sudo 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
sudo sed -i 's/nameserver .*/nameserver '${DNS_IP}'/g' /etc/resolv.conf
dig _kerberos._udp.TEST.GOKRB5
id: intgTestDeps

- name: Run Examples
run: |
cd ${GITHUB_WORKFLOW}
go run -tags="examples" examples/example.go
id: examples

- name: Tests including integration tests
run: |
cd ${GITHUB_WORKFLOW}
go test -race ./...
env:
INTEGRATION: 1
TESTPRIVILEGED: 1
id: intgTests

- name: Tests (32bit)
run: |
cd ${GITHUB_WORKFLOW}
go test ./...
env:
GOARCH: 386
INTEGRATION: 1
TESTPRIVILEGED: 1
id: test32
222 changes: 8 additions & 214 deletions README.md
@@ -1,9 +1,14 @@
# gokrb5
[![Version](https://img.shields.io/github/release/jcmturner/gokrb5.svg)](https://github.com/jcmturner/gokrb5/releases)

[![GoDoc](https://godoc.org/gopkg.in/jcmturner/gokrb5.v7?status.svg)](https://godoc.org/gopkg.in/jcmturner/gokrb5.v7) [![Go Report Card](https://goreportcard.com/badge/gopkg.in/jcmturner/gokrb5.v7)](https://goreportcard.com/report/gopkg.in/jcmturner/gokrb5.v7)
It is recommended to use the latest version: [![Version](https://img.shields.io/github/release/jcmturner/gokrb5.svg)](https://github.com/jcmturner/gokrb5/releases)

Development will be focused on the latest major version. New features will only be targeted at this version.

| Versions | Dependency Management | Import Path | Usage | Godoc | Go Report Card |
|----------|-----------------------|-------------|-------|-------|----------------|
| [![v8](https://github.com/jcmturner/gokrb5/workflows/v8/badge.svg)](https://github.com/jcmturner/gokrb5/actions?query=workflow%3Av8) | Go modules | import "github.com/jcmturner/gokrb5/v8/{sub-package}" | [![Usage](https://img.shields.io/badge/v8-usage-blue)](https://github.com/jcmturner/gokrb5/blob/master/v8/USAGE.md) | [![GoDoc](https://godoc.org/github.com/jcmturner/gokrb5/v8?status.svg)](https://godoc.org/github.com/jcmturner/gokrb5/v8) | [![Go Report Card](https://goreportcard.com/badge/github.com/jcmturner/gokrb5/v8)](https://goreportcard.com/report/github.com/jcmturner/gokrb5/v8) |
| [![v7](https://github.com/jcmturner/gokrb5/workflows/v7/badge.svg)](https://github.com/jcmturner/gokrb5/actions?query=workflow%3Av7) | gopkg.in | import "gopkg.in/jcmturner/gokrb5.v7/{sub-package}" | [![Usage](https://img.shields.io/badge/v7-usage-blue)](https://github.com/jcmturner/gokrb5/blob/master/USAGE.md) | [![GoDoc](https://godoc.org/gopkg.in/jcmturner/gokrb5.v7?status.svg)](https://godoc.org/gopkg.in/jcmturner/gokrb5.v7) | [![Go Report Card](https://goreportcard.com/badge/gopkg.in/jcmturner/gokrb5.v7)](https://goreportcard.com/report/gopkg.in/jcmturner/gokrb5.v7) |

[![Build Status](https://github.com/jcmturner/gokrb5/workflows/gokrb5/badge.svg)](https://github.com/jcmturner/gokrb5/actions)

#### Go Version Support
![Go version](https://img.shields.io/badge/Go-1.13-brightgreen.svg)
Expand All @@ -12,16 +17,6 @@

gokrb5 may work with other versions of Go but they are not tested.

### Go Get
To get the package, execute:
```
go get -d gopkg.in/jcmturner/gokrb5.v7/...
```
To import this package, add the following line to your code:
```go
import "gopkg.in/jcmturner/gokrb5.v7/<sub package>"
```

## Features
* **Pure Go** - no dependency on external libraries
* No platform specific code
Expand Down Expand Up @@ -58,207 +53,6 @@ The following is working/tested:
## Contributing
If you are interested in contributing to gokrb5, great! Please read the [contribution guidelines](https://github.com/jcmturner/gokrb5/blob/master/CONTRIBUTING.md).

## Usage

---

### Configuration
The gokrb5 libraries use the same krb5.conf configuration file format as MIT Kerberos, described [here](https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html).
Config instances can be created by loading from a file path or by passing a string, io.Reader or bufio.Scanner to the relevant method:
```go
import "gopkg.in/jcmturner/gokrb5.v7/config"
cfg, err := config.Load("/path/to/config/file")
cfg, err := config.NewConfigFromString(krb5Str) //String must have appropriate newline separations
cfg, err := config.NewConfigFromReader(reader)
cfg, err := config.NewConfigFromScanner(scanner)
```
### Keytab files
Standard keytab files can be read from a file or from a slice of bytes:
```go
import "gopkg.in/jcmturner/gokrb5.v7/keytab"
ktFromFile, err := keytab.Load("/path/to/file.keytab")
ktFromBytes, err := keytab.Parse(b)

```

---

### Kerberos Client
**Create** a client instance with either a password or a keytab.
A configuration must also be passed. Additionally optional additional settings can be provided.
```go
import "gopkg.in/jcmturner/gokrb5.v7/client"
cl := client.NewClientWithPassword("username", "REALM.COM", "password", cfg)
cl := client.NewClientWithKeytab("username", "REALM.COM", kt, cfg)
```
Optional settings are provided using the functions defined in the ``client/settings.go`` source file.

**Login**:
```go
err := cl.Login()
```
Kerberos Ticket Granting Tickets (TGT) will be automatically renewed unless the client was created from a CCache.

A client can be **destroyed** with the following method:
```go
cl.Destroy()
```

#### Active Directory KDC and FAST negotiation
Active Directory does not commonly support FAST negotiation so you will need to disable this on the client.
If this is the case you will see this error:
```KDC did not respond appropriately to FAST negotiation```
To resolve this disable PA-FX-Fast on the client before performing Login().
This is done with one of the optional client settings as shown below:
```go
cl := client.NewClientWithPassword("username", "REALM.COM", "password", cfg, client.DisablePAFXFAST(true))
```

#### Authenticate to a Service

##### HTTP SPNEGO
Create the HTTP request object and then create an SPNEGO client and use this to process the request with methods that
are the same as on a HTTP client.
If nil is passed as the HTTP client when creating the SPNEGO client the http.DefaultClient is used.
When creating the SPNEGO client pass the Service Principal Name (SPN) or auto generate the SPN from the request
object by passing a null string "".
```go
r, _ := http.NewRequest("GET", "http://host.test.gokrb5/index.html", nil)
spnegoCl := spnego.NewClient(cl, nil, "")
resp, err := spnegoCl.Do(r)
```

##### Generic Kerberos Client
To authenticate to a service a client will need to request a service ticket for a Service Principal Name (SPN) and form into an AP_REQ message along with an authenticator encrypted with the session key that was delivered from the KDC along with the service ticket.

The steps below outline how to do this.
* Get the service ticket and session key for the service the client is authenticating to.
The following method will use the client's cache either returning a valid cached ticket, renewing a cached ticket with the KDC or requesting a new ticket from the KDC.
Therefore the GetServiceTicket method can be continually used for the most efficient interaction with the KDC.
```go
tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
```

The steps after this will be specific to the application protocol but it will likely involve a client/server Authentication Protocol exchange (AP exchange).
This will involve these steps:

* Generate a new Authenticator and generate a sequence number and subkey:
```go
auth, _ := types.NewAuthenticator(cl.Credentials.Realm, cl.Credentials.CName)
etype, _ := crypto.GetEtype(key.KeyType)
auth.GenerateSeqNumberAndSubKey(key.KeyType, etype.GetKeyByteSize())
```
* Set the checksum on the authenticator
The checksum is an application specific value. Set as follows:
```go
auth.Cksum = types.Checksum{
CksumType: checksumIDint,
Checksum: checksumBytesSlice,
}
```
* Create the AP_REQ:
```go
APReq, err := messages.NewAPReq(tkt, key, auth)
```

Now send the AP_REQ to the service. How this is done will be specific to the application use case.

#### Changing a Client Password
This feature uses the Microsoft Kerberos Password Change protocol (RFC 3244).
This is implemented in Microsoft Active Directory and in MIT krb5kdc as of version 1.7.
Typically the kpasswd server listens on port 464.

Below is example code for how to use this feature:
```go
cfg, err := config.Load("/path/to/config/file")
if err != nil {
panic(err.Error())
}
kt, err := keytab.Load("/path/to/file.keytab")
if err != nil {
panic(err.Error())
}
cl := client.NewClientWithKeytab("username", "REALM.COM", kt)
cl.WithConfig(cfg)

ok, err := cl.ChangePasswd("newpassword")
if err != nil {
panic(err.Error())
}
if !ok {
panic("failed to change password")
}
```

The client kerberos config (krb5.conf) will need to have either the kpassd_server or admin_server defined in the relevant [realms] section.
For example:
```
REALM.COM = {
kdc = 127.0.0.1:88
kpasswd_server = 127.0.0.1:464
default_domain = realm.com
}
```
See https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms for more information.

---

### Kerberised Service

#### SPNEGO/Kerberos HTTP Service
A HTTP handler wrapper can be used to implement Kerberos SPNEGO authentication for web services.
To configure the wrapper the keytab for the SPN and a Logger are required:
```go
kt, err := keytab.Load("/path/to/file.keytab")
l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile)
```
Create a handler function of the application's handling method (apphandler in the example below):
```go
h := http.HandlerFunc(apphandler)
```
Configure the HTTP handler:
```go
http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l)))
```
The handler to be wrapped and the keytab are required arguments.
Additional optional settings can be provided, such as the logger shown above.

Another example of optional settings may be that when using Active Directory where the SPN is mapped to a user account
the keytab may contain an entry for this user account. In this case this should be specified as below with the ``KeytabPrincipal``:
```go
http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.KeytabPrincipal(pn)))
```

If authentication succeeds then the request's context will have the following values added so they can be accessed within the application's handler:
* spnego.CTXKeyAuthenticated - Boolean indicating if the user is authenticated. Use of this value should also handle that this value may not be set and should assume "false" in that case.
* spnego.CTXKeyCredentials - The authenticated user's credentials.
If Microsoft Active Directory is used as the KDC then additional ADCredentials are available in the credentials.Attributes map under the key credentials.AttributeKeyADCredentials. For example the SIDs of the users group membership are available and can be used by your application for authorization.

Access the credentials within your application:
```go
ctx := r.Context()
if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser {
if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok {
if ADCreds, ok := creds.Attributes()[credentials.AttributeKeyADCredentials].(credentials.ADCredentials); ok {
// Now access the fields of the ADCredentials struct. For example:
groupSids := ADCreds.GroupMembershipSIDs
}
}

}
```

#### Generic Kerberised Service - Validating Client Details
To validate the AP_REQ sent by the client on the service side call this method:
```go
import "gopkg.in/jcmturner/gokrb5.v7/service"
s := service.NewSettings(&kt) // kt is a keytab and optional settings can also be provided.
if ok, creds, err := service.VerifyAPREQ(APReq, s); ok {
// Perform application specific actions
// creds object has details about the client identity
}
```

---

## References
Expand Down

0 comments on commit 3196640

Please sign in to comment.