upload/koji: add support for GSSAPI/Kerberos auth
Prior this commit we only had support for username/password authentication in the koji integration. This wasn't particularly useful because this auth type isn't used in any production instance. This commit adds the support for GSSAPI/Kerberos authentication. The implementation uses kerby library which is very lightweight wrapper around C gssapi library. Also, the koji unit test and the run-koji-container script were modified so the GSSAPI auth is fully tested.
This commit is contained in:
parent
ecc7340570
commit
05fd221bd4
21 changed files with 1637 additions and 31 deletions
2
.github/koji.conf
vendored
2
.github/koji.conf
vendored
|
|
@ -66,3 +66,5 @@ plugins = runroot
|
|||
|
||||
; use the fast upload feature of koji by default
|
||||
use_fast_upload = yes
|
||||
|
||||
serverca = /tmp/osbuild-composer-koji-test/ca-crt.pem
|
||||
|
|
|
|||
7
.github/krb5.conf
vendored
Normal file
7
.github/krb5.conf
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
include /etc/krb5.conf
|
||||
|
||||
[realms]
|
||||
LOCAL = {
|
||||
kdc = localhost
|
||||
admin_server = localhost
|
||||
}
|
||||
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
|
|
@ -40,6 +40,10 @@ jobs:
|
|||
- name: Install golangci-lint
|
||||
run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.30.0
|
||||
|
||||
# This is needed to lint internal/upload/koji package
|
||||
- name: Install kerberos devel package
|
||||
run: sudo apt-get install -y libkrb5-dev
|
||||
|
||||
- name: Run golangci-lint
|
||||
run: $(go env GOPATH)/bin/golangci-lint run --timeout 5m0s
|
||||
|
||||
|
|
@ -73,7 +77,7 @@ jobs:
|
|||
# and installed here. See the last line of the script.
|
||||
- name: Install koji client
|
||||
run: |
|
||||
sudo apt-get install -y libkrb5-dev
|
||||
sudo apt-get install -y libkrb5-dev krb5-config
|
||||
python -m pip install --upgrade pip
|
||||
pip install koji
|
||||
sudo cp .github/koji.conf /etc/koji.conf
|
||||
|
|
@ -81,7 +85,7 @@ jobs:
|
|||
- name: Run unit tests
|
||||
run: |
|
||||
sudo internal/upload/koji/run-koji-container.sh start
|
||||
go test -v -race -covermode atomic -coverprofile=coverage.txt -tags koji_test ./internal/upload/koji
|
||||
env KRB5_CONFIG=../../../.github/krb5.conf go test -v -race -covermode atomic -coverprofile=coverage.txt -tags koji_test ./internal/upload/koji
|
||||
sudo internal/upload/koji/run-koji-container.sh stop
|
||||
|
||||
- name: Send coverage to codecov.io
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func main() {
|
|||
}
|
||||
defer file.Close()
|
||||
|
||||
k, err := koji.Login(server, "osbuild", "osbuildpass", http.DefaultTransport)
|
||||
k, err := koji.NewFromPlain(server, "osbuild", "osbuildpass", http.DefaultTransport)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -21,6 +21,7 @@ require (
|
|||
github.com/kolo/xmlrpc v0.0.0-20190909154602-56d5ec7c422e
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ubccr/kerby v0.0.0-20170626144437-201a958fc453
|
||||
github.com/vmware/govmomi v0.23.0
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -81,6 +81,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ubccr/kerby v0.0.0-20170626144437-201a958fc453 h1:rN0NwUFS6oK9ESlk2QyKfucb/gL4opUutNlCS2bBlvA=
|
||||
github.com/ubccr/kerby v0.0.0-20170626144437-201a958fc453/go.mod h1:s59e1aOY3F3KNsRx5W8cMdbtbt49aSKL7alLp6EKn48=
|
||||
github.com/vmware/govmomi v0.23.0 h1:DC97v1FdSr3cPfq3eBKD5C1O4JtYxo+NTcbGTKe2k48=
|
||||
github.com/vmware/govmomi v0.23.0/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc=
|
||||
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ import (
|
|||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/adler32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/kolo/xmlrpc"
|
||||
"github.com/ubccr/kerby/khttp"
|
||||
)
|
||||
|
||||
type Koji struct {
|
||||
|
|
@ -106,26 +109,17 @@ type CGImportResult struct {
|
|||
BuildID int `xmlrpc:"build_id"`
|
||||
}
|
||||
|
||||
func Login(server, user, password string, transport http.RoundTripper) (*Koji, error) {
|
||||
// Create a temporary xmlrpc client.
|
||||
// The API doesn't require sessionID, sessionKey and callnum yet,
|
||||
// so there's no need to use the custom Koji RoundTripper,
|
||||
// let's just use the one that the called passed in.
|
||||
loginClient, err := xmlrpc.NewClient(server, http.DefaultTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type GSSAPICredentials struct {
|
||||
Principal string
|
||||
KeyTab string
|
||||
}
|
||||
|
||||
args := []interface{}{user, password}
|
||||
var reply struct {
|
||||
type loginReply struct {
|
||||
SessionID int64 `xmlrpc:"session-id"`
|
||||
SessionKey string `xmlrpc:"session-key"`
|
||||
}
|
||||
err = loginClient.Call("login", args, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func newKoji(server string, transport http.RoundTripper, reply loginReply) (*Koji, error) {
|
||||
// Create the final xmlrpc client with our custom RoundTripper handling
|
||||
// sessionID, sessionKey and callnum
|
||||
kojiTransport := &Transport{
|
||||
|
|
@ -134,7 +128,6 @@ func Login(server, user, password string, transport http.RoundTripper) (*Koji, e
|
|||
callnum: 0,
|
||||
transport: transport,
|
||||
}
|
||||
|
||||
client, err := xmlrpc.NewClient(server, kojiTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -147,6 +140,55 @@ func Login(server, user, password string, transport http.RoundTripper) (*Koji, e
|
|||
}, nil
|
||||
}
|
||||
|
||||
// NewFromPlain creates a new Koji sessions =authenticated using the plain
|
||||
// username/password method. If you want to speak to a public koji instance,
|
||||
// you probably cannot use this method.
|
||||
func NewFromPlain(server, user, password string, transport http.RoundTripper) (*Koji, error) {
|
||||
// Create a temporary xmlrpc client.
|
||||
// The API doesn't require sessionID, sessionKey and callnum yet,
|
||||
// so there's no need to use the custom Koji RoundTripper,
|
||||
// let's just use the one that the called passed in.
|
||||
loginClient, err := xmlrpc.NewClient(server, http.DefaultTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args := []interface{}{user, password}
|
||||
var reply loginReply
|
||||
err = loginClient.Call("login", args, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newKoji(server, transport, reply)
|
||||
}
|
||||
|
||||
// NewFromGSSAPI creates a new Koji session authenticated using GSSAPI.
|
||||
// Principal and keytab used for the session is passed using credentials
|
||||
// parameter.
|
||||
func NewFromGSSAPI(server string, credentials *GSSAPICredentials, transport http.RoundTripper) (*Koji, error) {
|
||||
// Create a temporary xmlrpc client with kerberos transport.
|
||||
// The API doesn't require sessionID, sessionKey and callnum yet,
|
||||
// so there's no need to use the custom Koji RoundTripper,
|
||||
// let's just use the one that the called passed in.
|
||||
loginClient, err := xmlrpc.NewClient(server+"/ssllogin", &khttp.Transport{
|
||||
KeyTab: credentials.KeyTab,
|
||||
Principal: credentials.Principal,
|
||||
Next: transport,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reply loginReply
|
||||
err = loginClient.Call("sslLogin", nil, &reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newKoji(server, transport, reply)
|
||||
}
|
||||
|
||||
// GetAPIVersion gets the version of the API of the remote Koji instance
|
||||
func (k *Koji) GetAPIVersion() (int, error) {
|
||||
var version int
|
||||
|
|
@ -309,3 +351,17 @@ func (rt *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
|
||||
return rt.transport.RoundTrip(rClone)
|
||||
}
|
||||
|
||||
func GSSAPICredentialsFromEnv() (*GSSAPICredentials, error) {
|
||||
principal, principalExists := os.LookupEnv("OSBUILD_COMPOSER_KOJI_PRINCIPAL")
|
||||
keyTab, keyTabExists := os.LookupEnv("OSBUILD_COMPOSER_KOJI_KEYTAB")
|
||||
|
||||
if !principalExists || !keyTabExists {
|
||||
return nil, errors.New("Both OSBUILD_COMPOSER_KOJI_PRINCIPAL and OSBUILD_COMPOSER_KOJI_KEYTAB must be set")
|
||||
}
|
||||
|
||||
return &GSSAPICredentials{
|
||||
Principal: principal,
|
||||
KeyTab: keyTab,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ package koji_test
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
|
@ -22,17 +24,36 @@ import (
|
|||
|
||||
func TestKojiImport(t *testing.T) {
|
||||
// define constants
|
||||
server := "http://localhost:8080/kojihub"
|
||||
user := "osbuild"
|
||||
password := "osbuildpass"
|
||||
server := "https://localhost/kojihub"
|
||||
filename := "image.qcow2"
|
||||
filesize := 1024
|
||||
shareDir := "/tmp/osbuild-composer-koji-test"
|
||||
// you cannot create two build with a same name, let's create a random one each time
|
||||
buildName := "osbuild-image-" + uuid.Must(uuid.NewRandom()).String()
|
||||
// koji needs to specify a directory to which the upload should happen, let's reuse the build name
|
||||
uploadDirectory := buildName
|
||||
|
||||
k, err := koji.Login(server, user, password, http.DefaultTransport)
|
||||
// base our transport on the default one
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
// use the self-signed certificate generated by run-koji-container
|
||||
certPool := x509.NewCertPool()
|
||||
cert, err := ioutil.ReadFile(shareDir + "/ca-crt.pem")
|
||||
require.NoError(t, err)
|
||||
|
||||
ok := certPool.AppendCertsFromPEM(cert)
|
||||
require.True(t, ok)
|
||||
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: certPool,
|
||||
}
|
||||
|
||||
// login
|
||||
credentials := &koji.GSSAPICredentials{
|
||||
Principal: "osbuild-krb@LOCAL",
|
||||
KeyTab: shareDir + "/client.keytab",
|
||||
}
|
||||
k, err := koji.NewFromGSSAPI(server, credentials, transport)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
|
|
@ -111,9 +132,9 @@ func TestKojiImport(t *testing.T) {
|
|||
cmd := exec.Command(
|
||||
"koji",
|
||||
"--server", server,
|
||||
"--user", user,
|
||||
"--password", password,
|
||||
"--authtype", "password",
|
||||
"-c", "../../../.github/koji.conf",
|
||||
"--keytab", credentials.KeyTab,
|
||||
"--principal", credentials.Principal,
|
||||
"list-builds",
|
||||
"--buildid", strconv.Itoa(result.BuildID),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,19 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
SHARE_DIR=/tmp/osbuild-composer-koji-test
|
||||
|
||||
koji_stop () {
|
||||
echo "Shutting down containers, please wait..."
|
||||
|
||||
${CONTAINER_RUNTIME} stop org.osbuild.koji.koji || true
|
||||
${CONTAINER_RUNTIME} rm org.osbuild.koji.koji || true
|
||||
|
||||
${CONTAINER_RUNTIME} stop org.osbuild.koji.kdc || true
|
||||
${CONTAINER_RUNTIME} rm org.osbuild.koji.kdc || true
|
||||
|
||||
${CONTAINER_RUNTIME} stop org.osbuild.koji.postgres || true
|
||||
${CONTAINER_RUNTIME} rm org.osbuild.koji.postgres || true
|
||||
|
||||
${CONTAINER_RUNTIME} network rm -f org.osbuild.koji || true
|
||||
|
||||
rm -rf "${SHARE_DIR}" || true
|
||||
}
|
||||
|
||||
koji_clean_up_bad_start () {
|
||||
# remember the exit code, so we can report it later
|
||||
EXIT_CODE=$?
|
||||
echo "Start failed, removing containers."
|
||||
|
||||
|
|
@ -22,23 +30,80 @@ koji_clean_up_bad_start () {
|
|||
exit $EXIT_CODE
|
||||
}
|
||||
|
||||
|
||||
# helper to simplify sql queries to the postgres instance
|
||||
psql_cmd () {
|
||||
${CONTAINER_RUNTIME} exec org.osbuild.koji.postgres psql -U koji -d koji "$@"
|
||||
}
|
||||
|
||||
# helper to simplify running commands in the kdc container
|
||||
kdc_exec() {
|
||||
${CONTAINER_RUNTIME} exec org.osbuild.koji.kdc "$@"
|
||||
}
|
||||
|
||||
koji_start() {
|
||||
trap koji_clean_up_bad_start EXIT
|
||||
|
||||
# create a share directory which is used to share files between the host and containers
|
||||
mkdir "${SHARE_DIR}"
|
||||
|
||||
# generate self-signed certificates in the share directory
|
||||
openssl req -new -nodes -x509 -days 365 -keyout "${SHARE_DIR}/ca-key.pem" -out "${SHARE_DIR}/ca-crt.pem" -subj "/CN=osbuild.org"
|
||||
openssl genrsa -out "${SHARE_DIR}/key.pem" 2048
|
||||
openssl req -new -sha256 -key "${SHARE_DIR}/key.pem" -out "${SHARE_DIR}/csr.pem" -subj "/CN=localhost"
|
||||
openssl x509 -req -in "${SHARE_DIR}/csr.pem" -CA "${SHARE_DIR}/ca-crt.pem" -CAkey "${SHARE_DIR}/ca-key.pem" -CAcreateserial -out "${SHARE_DIR}/crt.pem"
|
||||
|
||||
${CONTAINER_RUNTIME} network create org.osbuild.koji
|
||||
|
||||
${CONTAINER_RUNTIME} run -d --name org.osbuild.koji.postgres --network org.osbuild.koji \
|
||||
-e POSTGRES_USER=koji \
|
||||
-e POSTGRES_PASSWORD=kojipass \
|
||||
-e POSTGRES_DB=koji \
|
||||
docker.io/library/postgres:12-alpine
|
||||
|
||||
${CONTAINER_RUNTIME} run -d --name org.osbuild.koji.kdc \
|
||||
--network org.osbuild.koji \
|
||||
-v "${SHARE_DIR}:/share:z" \
|
||||
-p 88:88/udp \
|
||||
quay.io/osbuild/kdc:v1
|
||||
|
||||
# initialize krb pricipals and create keytabs for them
|
||||
# HTTP/localhost@LOCAL for kojihub
|
||||
kdc_exec kadmin.local -r LOCAL add_principal -randkey HTTP/localhost@LOCAL
|
||||
kdc_exec kadmin.local -r LOCAL ktadd -k /share/koji.keytab HTTP/localhost@LOCAL
|
||||
kdc_exec chmod 644 /share/koji.keytab
|
||||
|
||||
# osbuild-krb@LOCAL for koji clients
|
||||
kdc_exec kadmin.local -r LOCAL add_principal -randkey osbuild-krb@LOCAL
|
||||
kdc_exec kadmin.local -r LOCAL ktadd -k /share/client.keytab osbuild-krb@LOCAL
|
||||
kdc_exec chmod 644 /share/client.keytab
|
||||
|
||||
${CONTAINER_RUNTIME} run -d --name org.osbuild.koji.koji --network org.osbuild.koji \
|
||||
-p 8080:80 \
|
||||
-v "${SHARE_DIR}:/share:z" \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
-e POSTGRES_USER=koji \
|
||||
-e POSTGRES_PASSWORD=kojipass \
|
||||
-e POSTGRES_DB=koji \
|
||||
-e POSTGRES_HOST=org.osbuild.koji.postgres \
|
||||
quay.io/osbuild/ghci-koji:v1
|
||||
quay.io/osbuild/koji:v1
|
||||
|
||||
# TODO: we need to wait for the database to be initialized here. A better method should be used.
|
||||
sleep 2
|
||||
|
||||
# create koji users
|
||||
# kojiadmin/kojipass - admin
|
||||
# osbuild/osbuildpass - regular user
|
||||
# osbuild-krb: - regular user authenticated with Kerberos principal osbuild-krb@LOCAL
|
||||
psql_cmd -c "insert into users (name, password, status, usertype) values ('kojiadmin', 'kojipass', 0, 0)" >/dev/null
|
||||
psql_cmd -c "insert into user_perms (user_id, perm_id, creator_id) values (1, 1, 1)" >/dev/null
|
||||
psql_cmd -c "insert into users (name, password, status, usertype) values ('osbuild', 'osbuildpass', 0, 0)" >/dev/null
|
||||
psql_cmd -c "insert into users (name, status, usertype) values ('osbuild-krb', 0, 0)" >/dev/null
|
||||
psql_cmd -c "insert into user_krb_principals (user_id, krb_principal) values (3, 'osbuild-krb@LOCAL')" >/dev/null
|
||||
|
||||
# create content generator osbuild, give osbuild and osbuild-krb users access to it
|
||||
psql_cmd -c "insert into content_generator (name) values ('osbuild')" >/dev/null
|
||||
psql_cmd -c "insert into cg_users (cg_id, user_id, creator_id, active) values (1, 2, 1, true), (1, 3, 1, true)" >/dev/null
|
||||
|
||||
echo "Containers are running, to stop them use:"
|
||||
echo "$0 stop"
|
||||
|
|
@ -46,6 +111,7 @@ koji_start() {
|
|||
trap - EXIT
|
||||
}
|
||||
|
||||
# check arguments
|
||||
if [[ $# -ne 1 || ( "$1" != "start" && "$1" != "stop" ) ]]; then
|
||||
cat <<DOC
|
||||
usage: $0 start|stop
|
||||
|
|
@ -56,11 +122,13 @@ DOC
|
|||
exit 3
|
||||
fi
|
||||
|
||||
# this script must be run as root
|
||||
if [ $UID != 0 ]; then
|
||||
echo This script must be run as root.
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# decide whether podman or docker should be used
|
||||
if which podman 2>/dev/null >&2; then
|
||||
CONTAINER_RUNTIME=podman
|
||||
elif which docker 2>/dev/null >&2; then
|
||||
|
|
|
|||
7
krb5.conf
Normal file
7
krb5.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
include /etc/krb5.conf
|
||||
|
||||
[realms]
|
||||
LOCAL = {
|
||||
kdc = localhost
|
||||
admin_server = localhost
|
||||
}
|
||||
1
vendor/github.com/ubccr/kerby/.gitignore
generated
vendored
Normal file
1
vendor/github.com/ubccr/kerby/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
cmd/kerby/kerby
|
||||
202
vendor/github.com/ubccr/kerby/LICENSE
generated
vendored
Normal file
202
vendor/github.com/ubccr/kerby/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
136
vendor/github.com/ubccr/kerby/README.rst
generated
vendored
Normal file
136
vendor/github.com/ubccr/kerby/README.rst
generated
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
===============================================================================
|
||||
Kerby - Go wrapper for Kerberos GSSAPI
|
||||
===============================================================================
|
||||
|
||||
|godoc|
|
||||
|
||||
This is a port of the PyKerberos library in Go. The main motivation for this
|
||||
library was to provide HTTP client authentication using Kerberos. The khttp
|
||||
package provides a transport that authenticates all outgoing requests using
|
||||
SPNEGO (negotiate authentication) http://tools.ietf.org/html/rfc4559.
|
||||
|
||||
The C code is adapted from PyKerberos http://calendarserver.org/wiki/PyKerberos.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
Usage
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Note: You need the have the krb5-libs/GSSAPI packages installed for your OS.
|
||||
|
||||
Install using go tools::
|
||||
|
||||
$ go get github.com/ubccr/kerby
|
||||
|
||||
To run the unit tests you must have a valid Kerberos setup on the test machine
|
||||
and you should ensure that you have valid Kerberos tickets (run 'klist' on the
|
||||
command line). If you're authentication using a client keytab file you can
|
||||
optionally export the env variable KRB5_CLIENT_KTNAME::
|
||||
|
||||
$ export KRB5_CLIENT_KTNAME=/path/to/client.keytab
|
||||
$ export KERBY_TEST_SERVICE="service@REALM"
|
||||
$ export KERBY_TEST_PRINC="princ@REALM"
|
||||
$ go test
|
||||
|
||||
Example HTTP Kerberos client authentication using a client keytab file::
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/ubccr/kerby/khttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
payload := []byte(`{"method":"hello_world"}`)
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
"https://server.example.com/json",
|
||||
bytes.NewBuffer(payload))
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
t := &khttp.Transport{
|
||||
KeyTab: "/path/to/client.keytab",
|
||||
Principal: "principal@REALM"}
|
||||
|
||||
client := &http.Client{Transport: t}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%d\n", res.StatusCode)
|
||||
fmt.Printf("%s", data)
|
||||
}
|
||||
|
||||
Example HTTP handler supporting Kerberose authentication::
|
||||
|
||||
func handler(w http.ResponseWriter, req *http.Request) {
|
||||
authReq := strings.Split(req.Header.Get(authorizationHeader), " ")
|
||||
if len(authReq) != 2 || authReq[0] != negotiateHeader {
|
||||
w.Header().Set(wwwAuthenticateHeader, negotiateHeader)
|
||||
http.Error(w, "Invalid authorization header", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ks := new(kerby.KerbServer)
|
||||
err := ks.Init("")
|
||||
if err != nil {
|
||||
log.Printf("KerbServer Init Error: %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer ks.Clean()
|
||||
|
||||
|
||||
err = ks.Step(authReq[1])
|
||||
w.Header().Set(wwwAuthenticateHeader, negotiateHeader+" "+ks.Response())
|
||||
|
||||
if err != nil {
|
||||
log.Printf("KerbServer Step Error: %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user := ks.UserName()
|
||||
fmt.Fprintf(w, "Hello, %s", user)
|
||||
}
|
||||
|
||||
Example adding Kerberos authentication to an http.FileServer using khttp.Handler::
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/ubccr/kerby/khttp"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.Handle("/", khttp.Handler(http.FileServer(http.Dir("/tmp"))))
|
||||
log.Fatal(http.ListenAndServe(":8000", nil))
|
||||
}
|
||||
|
||||
------------------------------------------------------------------------
|
||||
License
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Kerby is released under the Apache 2.0 License. See the LICENSE file.
|
||||
|
||||
|
||||
|
||||
.. |godoc| image:: https://godoc.org/github.com/golang/gddo?status.svg
|
||||
:target: https://godoc.org/github.com/ubccr/kerby
|
||||
:alt: Godoc
|
||||
|
||||
125
vendor/github.com/ubccr/kerby/base64.c
generated
vendored
Normal file
125
vendor/github.com/ubccr/kerby/base64.c
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Copyright (c) 2006-2015 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// base64 tables
|
||||
static char basis_64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static signed char index_64[128] =
|
||||
{
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
|
||||
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
|
||||
};
|
||||
#define CHAR64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
|
||||
|
||||
// base64_encode : base64 encode
|
||||
//
|
||||
// value : data to encode
|
||||
// vlen : length of data
|
||||
// (result) : new char[] - c-str of result
|
||||
char *base64_encode(const unsigned char *value, size_t vlen)
|
||||
{
|
||||
char *result = (char *)malloc((vlen * 4) / 3 + 5);
|
||||
char *out = result;
|
||||
while (vlen >= 3)
|
||||
{
|
||||
*out++ = basis_64[value[0] >> 2];
|
||||
*out++ = basis_64[((value[0] << 4) & 0x30) | (value[1] >> 4)];
|
||||
*out++ = basis_64[((value[1] << 2) & 0x3C) | (value[2] >> 6)];
|
||||
*out++ = basis_64[value[2] & 0x3F];
|
||||
value += 3;
|
||||
vlen -= 3;
|
||||
}
|
||||
if (vlen > 0)
|
||||
{
|
||||
*out++ = basis_64[value[0] >> 2];
|
||||
unsigned char oval = (value[0] << 4) & 0x30;
|
||||
if (vlen > 1) oval |= value[1] >> 4;
|
||||
*out++ = basis_64[oval];
|
||||
*out++ = (vlen < 2) ? '=' : basis_64[(value[1] << 2) & 0x3C];
|
||||
*out++ = '=';
|
||||
}
|
||||
*out = '\0';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// base64_decode : base64 decode
|
||||
//
|
||||
// value : c-str to decode
|
||||
// rlen : length of decoded result
|
||||
// (result) : new unsigned char[] - decoded result
|
||||
unsigned char *base64_decode(const char *value, size_t *rlen)
|
||||
{
|
||||
*rlen = 0;
|
||||
int c1, c2, c3, c4;
|
||||
|
||||
size_t vlen = strlen(value);
|
||||
unsigned char *result =(unsigned char *)malloc((vlen * 3) / 4 + 1);
|
||||
unsigned char *out = result;
|
||||
|
||||
while (1) {
|
||||
if (value[0]==0) {
|
||||
return result;
|
||||
}
|
||||
c1 = value[0];
|
||||
if (CHAR64(c1) == -1) {
|
||||
goto base64_decode_error;;
|
||||
}
|
||||
c2 = value[1];
|
||||
if (CHAR64(c2) == -1) {
|
||||
goto base64_decode_error;;
|
||||
}
|
||||
c3 = value[2];
|
||||
if ((c3 != '=') && (CHAR64(c3) == -1)) {
|
||||
goto base64_decode_error;;
|
||||
}
|
||||
c4 = value[3];
|
||||
if ((c4 != '=') && (CHAR64(c4) == -1)) {
|
||||
goto base64_decode_error;;
|
||||
}
|
||||
|
||||
value += 4;
|
||||
*out++ = (CHAR64(c1) << 2) | (CHAR64(c2) >> 4);
|
||||
*rlen += 1;
|
||||
|
||||
if (c3 != '=') {
|
||||
*out++ = ((CHAR64(c2) << 4) & 0xf0) | (CHAR64(c3) >> 2);
|
||||
*rlen += 1;
|
||||
|
||||
if (c4 != '=') {
|
||||
*out++ = ((CHAR64(c3) << 6) & 0xc0) | CHAR64(c4);
|
||||
*rlen += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base64_decode_error:
|
||||
*result = 0;
|
||||
*rlen = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
20
vendor/github.com/ubccr/kerby/base64.h
generated
vendored
Normal file
20
vendor/github.com/ubccr/kerby/base64.h
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (c) 2006-2015 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
char *base64_encode(const unsigned char *value, size_t vlen);
|
||||
unsigned char *base64_decode(const char *value, size_t *rlen);
|
||||
461
vendor/github.com/ubccr/kerby/kerberosgss.c
generated
vendored
Normal file
461
vendor/github.com/ubccr/kerby/kerberosgss.c
generated
vendored
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
/**
|
||||
* Adopted from PyKerberos. Modified for use with Kerby.
|
||||
*
|
||||
* Copyright (c) 2006-2015 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
#include "kerberosgss.h"
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
gss_client_state* new_gss_client_state() {
|
||||
gss_client_state *state;
|
||||
|
||||
state = (gss_client_state *) malloc(sizeof(gss_client_state));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
gss_server_state* new_gss_server_state() {
|
||||
gss_server_state *state;
|
||||
|
||||
state = (gss_server_state *) malloc(sizeof(gss_server_state));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void free_gss_client_state(gss_client_state *state) {
|
||||
free(state);
|
||||
}
|
||||
|
||||
void free_gss_server_state(gss_server_state *state) {
|
||||
free(state);
|
||||
}
|
||||
|
||||
int authenticate_gss_client_init(
|
||||
const char* service, const char* principal, long int gss_flags,
|
||||
gss_server_state* delegatestate, gss_client_state* state
|
||||
)
|
||||
{
|
||||
gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
|
||||
gss_buffer_desc principal_token = GSS_C_EMPTY_BUFFER;
|
||||
int ret = AUTH_GSS_COMPLETE;
|
||||
|
||||
state->server_name = GSS_C_NO_NAME;
|
||||
state->context = GSS_C_NO_CONTEXT;
|
||||
state->gss_flags = gss_flags;
|
||||
state->client_creds = GSS_C_NO_CREDENTIAL;
|
||||
state->username = NULL;
|
||||
state->response = NULL;
|
||||
|
||||
// Import server name first
|
||||
name_token.length = strlen(service);
|
||||
name_token.value = (char *)service;
|
||||
|
||||
state->maj_stat = gss_import_name(
|
||||
&state->min_stat, &name_token, gss_krb5_nt_service_name, &state->server_name
|
||||
);
|
||||
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
// Use the delegate credentials if they exist
|
||||
if (delegatestate && delegatestate->client_creds != GSS_C_NO_CREDENTIAL) {
|
||||
state->client_creds = delegatestate->client_creds;
|
||||
}
|
||||
// If available use the principal to extract its associated credentials
|
||||
else if (principal && *principal) {
|
||||
gss_name_t name;
|
||||
principal_token.length = strlen(principal);
|
||||
principal_token.value = (char *)principal;
|
||||
|
||||
state->maj_stat = gss_import_name(
|
||||
&state->min_stat, &principal_token, GSS_C_NT_USER_NAME, &name
|
||||
);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
state->maj_stat = gss_acquire_cred(
|
||||
&state->min_stat, name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
|
||||
GSS_C_INITIATE, &state->client_creds, NULL, NULL
|
||||
);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
state->maj_stat = gss_release_name(&state->min_stat, &name);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int authenticate_gss_client_clean(gss_client_state *state)
|
||||
{
|
||||
OM_uint32 maj_stat;
|
||||
OM_uint32 min_stat;
|
||||
int ret = AUTH_GSS_COMPLETE;
|
||||
|
||||
if (state->context != GSS_C_NO_CONTEXT) {
|
||||
maj_stat = gss_delete_sec_context(
|
||||
&min_stat, &state->context, GSS_C_NO_BUFFER
|
||||
);
|
||||
}
|
||||
if (state->server_name != GSS_C_NO_NAME) {
|
||||
maj_stat = gss_release_name(&min_stat, &state->server_name);
|
||||
}
|
||||
if (
|
||||
state->client_creds != GSS_C_NO_CREDENTIAL &&
|
||||
! (state->gss_flags & GSS_C_DELEG_FLAG)
|
||||
) {
|
||||
maj_stat = gss_release_cred(&min_stat, &state->client_creds);
|
||||
}
|
||||
if (state->username != NULL) {
|
||||
free(state->username);
|
||||
state->username = NULL;
|
||||
}
|
||||
if (state->response != NULL) {
|
||||
free(state->response);
|
||||
state->response = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int authenticate_gss_client_step(
|
||||
gss_client_state* state, const char* challenge
|
||||
) {
|
||||
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
|
||||
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
|
||||
int ret = AUTH_GSS_CONTINUE;
|
||||
|
||||
// Always clear out the old response
|
||||
if (state->response != NULL) {
|
||||
free(state->response);
|
||||
state->response = NULL;
|
||||
}
|
||||
|
||||
// If there is a challenge (data from the server) we need to give it to GSS
|
||||
if (challenge && *challenge) {
|
||||
size_t len;
|
||||
input_token.value = base64_decode(challenge, &len);
|
||||
input_token.length = len;
|
||||
}
|
||||
|
||||
// Do GSSAPI step
|
||||
state->maj_stat = gss_init_sec_context(
|
||||
&state->min_stat,
|
||||
state->client_creds,
|
||||
&state->context,
|
||||
state->server_name,
|
||||
GSS_C_NO_OID,
|
||||
(OM_uint32)state->gss_flags,
|
||||
0,
|
||||
GSS_C_NO_CHANNEL_BINDINGS,
|
||||
&input_token,
|
||||
NULL,
|
||||
&output_token,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
if ((state->maj_stat != GSS_S_COMPLETE) && (state->maj_stat != GSS_S_CONTINUE_NEEDED)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = (state->maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE;
|
||||
// Grab the client response to send back to the server
|
||||
if (output_token.length) {
|
||||
state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);;
|
||||
state->maj_stat = gss_release_buffer(&state->min_stat, &output_token);
|
||||
}
|
||||
|
||||
// Try to get the user name if we have completed all GSS operations
|
||||
if (ret == AUTH_GSS_COMPLETE) {
|
||||
gss_name_t gssuser = GSS_C_NO_NAME;
|
||||
state->maj_stat = gss_inquire_context(&state->min_stat, state->context, &gssuser, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
gss_buffer_desc name_token;
|
||||
name_token.length = 0;
|
||||
state->maj_stat = gss_display_name(&state->min_stat, gssuser, &name_token, NULL);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
if (name_token.value)
|
||||
gss_release_buffer(&state->min_stat, &name_token);
|
||||
gss_release_name(&state->min_stat, &gssuser);
|
||||
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
} else {
|
||||
state->username = (char *)malloc(name_token.length + 1);
|
||||
strncpy(state->username, (char*) name_token.value, name_token.length);
|
||||
state->username[name_token.length] = 0;
|
||||
gss_release_buffer(&state->min_stat, &name_token);
|
||||
gss_release_name(&state->min_stat, &gssuser);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
if (output_token.value) {
|
||||
gss_release_buffer(&state->min_stat, &output_token);
|
||||
}
|
||||
if (input_token.value) {
|
||||
free(input_token.value);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int authenticate_gss_server_init(const char *service, gss_server_state *state)
|
||||
{
|
||||
gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
|
||||
int ret = AUTH_GSS_COMPLETE;
|
||||
|
||||
state->context = GSS_C_NO_CONTEXT;
|
||||
state->server_name = GSS_C_NO_NAME;
|
||||
state->client_name = GSS_C_NO_NAME;
|
||||
state->server_creds = GSS_C_NO_CREDENTIAL;
|
||||
state->client_creds = GSS_C_NO_CREDENTIAL;
|
||||
state->username = NULL;
|
||||
state->targetname = NULL;
|
||||
state->response = NULL;
|
||||
state->ccname = NULL;
|
||||
|
||||
// Server name may be empty which means we aren't going to create our own creds
|
||||
size_t service_len = strlen(service);
|
||||
if (service_len != 0) {
|
||||
// Import server name first
|
||||
name_token.length = strlen(service);
|
||||
name_token.value = (char *)service;
|
||||
|
||||
state->maj_stat = gss_import_name(
|
||||
&state->min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE,
|
||||
&state->server_name
|
||||
);
|
||||
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
// Get credentials
|
||||
state->maj_stat = gss_acquire_cred(
|
||||
&state->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
|
||||
GSS_C_BOTH, &state->server_creds, NULL, NULL
|
||||
);
|
||||
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int authenticate_gss_server_clean(gss_server_state *state)
|
||||
{
|
||||
int ret = AUTH_GSS_COMPLETE;
|
||||
|
||||
if (state->context != GSS_C_NO_CONTEXT) {
|
||||
state->maj_stat = gss_delete_sec_context(
|
||||
&state->min_stat, &state->context, GSS_C_NO_BUFFER
|
||||
);
|
||||
}
|
||||
if (state->server_name != GSS_C_NO_NAME) {
|
||||
state->maj_stat = gss_release_name(&state->min_stat, &state->server_name);
|
||||
}
|
||||
if (state->client_name != GSS_C_NO_NAME) {
|
||||
state->maj_stat = gss_release_name(&state->min_stat, &state->client_name);
|
||||
}
|
||||
if (state->server_creds != GSS_C_NO_CREDENTIAL) {
|
||||
state->maj_stat = gss_release_cred(&state->min_stat, &state->server_creds);
|
||||
}
|
||||
if (state->client_creds != GSS_C_NO_CREDENTIAL) {
|
||||
state->maj_stat = gss_release_cred(&state->min_stat, &state->client_creds);
|
||||
}
|
||||
if (state->username != NULL) {
|
||||
free(state->username);
|
||||
state->username = NULL;
|
||||
}
|
||||
if (state->targetname != NULL) {
|
||||
free(state->targetname);
|
||||
state->targetname = NULL;
|
||||
}
|
||||
if (state->response != NULL) {
|
||||
free(state->response);
|
||||
state->response = NULL;
|
||||
}
|
||||
if (state->ccname != NULL) {
|
||||
free(state->ccname);
|
||||
state->ccname = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int authenticate_gss_server_step(
|
||||
gss_server_state *state, const char *challenge
|
||||
) {
|
||||
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
|
||||
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
|
||||
int ret = AUTH_GSS_CONTINUE;
|
||||
|
||||
// Always clear out the old response
|
||||
if (state->response != NULL) {
|
||||
free(state->response);
|
||||
state->response = NULL;
|
||||
}
|
||||
|
||||
// If there is a challenge (data from the server) we need to give it to GSS
|
||||
if (challenge && *challenge) {
|
||||
size_t len;
|
||||
input_token.value = base64_decode(challenge, &len);
|
||||
input_token.length = len;
|
||||
} else {
|
||||
// XXX No challenge parameter in request from client
|
||||
// XXX How to pass error string to state?
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
state->maj_stat = gss_accept_sec_context(
|
||||
&state->min_stat,
|
||||
&state->context,
|
||||
state->server_creds,
|
||||
&input_token,
|
||||
GSS_C_NO_CHANNEL_BINDINGS,
|
||||
&state->client_name,
|
||||
NULL,
|
||||
&output_token,
|
||||
NULL,
|
||||
NULL,
|
||||
&state->client_creds
|
||||
);
|
||||
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
// Grab the server response to send back to the client
|
||||
if (output_token.length) {
|
||||
state->response = base64_encode(
|
||||
(const unsigned char *)output_token.value, output_token.length
|
||||
);;
|
||||
state->maj_stat = gss_release_buffer(&state->min_stat, &output_token);
|
||||
}
|
||||
|
||||
// Get the user name
|
||||
state->maj_stat = gss_display_name(
|
||||
&state->min_stat, state->client_name, &output_token, NULL
|
||||
);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
state->username = (char *)malloc(output_token.length + 1);
|
||||
strncpy(state->username, (char*) output_token.value, output_token.length);
|
||||
state->username[output_token.length] = 0;
|
||||
|
||||
// Get the target name if no server creds were supplied
|
||||
if (state->server_creds == GSS_C_NO_CREDENTIAL) {
|
||||
gss_name_t target_name = GSS_C_NO_NAME;
|
||||
state->maj_stat = gss_inquire_context(
|
||||
&state->min_stat, state->context, NULL, &target_name, NULL, NULL, NULL,
|
||||
NULL, NULL
|
||||
);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
state->maj_stat = gss_display_name(
|
||||
&state->min_stat, target_name, &output_token, NULL
|
||||
);
|
||||
if (GSS_ERROR(state->maj_stat)) {
|
||||
ret = AUTH_GSS_ERROR;
|
||||
goto end;
|
||||
}
|
||||
state->targetname = (char *)malloc(output_token.length + 1);
|
||||
strncpy(
|
||||
state->targetname, (char*) output_token.value, output_token.length
|
||||
);
|
||||
state->targetname[output_token.length] = 0;
|
||||
}
|
||||
|
||||
ret = AUTH_GSS_COMPLETE;
|
||||
|
||||
end:
|
||||
if (output_token.length) {
|
||||
gss_release_buffer(&state->min_stat, &output_token);
|
||||
}
|
||||
if (input_token.value) {
|
||||
free(input_token.value);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void get_gss_error(OM_uint32 err_maj, char *buf_maj, OM_uint32 err_min, char *buf_min)
|
||||
{
|
||||
OM_uint32 maj_stat, min_stat;
|
||||
OM_uint32 msg_ctx = 0;
|
||||
gss_buffer_desc status_string;
|
||||
|
||||
do {
|
||||
maj_stat = gss_display_status(
|
||||
&min_stat,
|
||||
err_maj,
|
||||
GSS_C_GSS_CODE,
|
||||
GSS_C_NO_OID,
|
||||
&msg_ctx,
|
||||
&status_string
|
||||
);
|
||||
if (GSS_ERROR(maj_stat)) {
|
||||
break;
|
||||
}
|
||||
strncpy(buf_maj, (char*) status_string.value, GSS_ERRBUF_SIZE);
|
||||
gss_release_buffer(&min_stat, &status_string);
|
||||
|
||||
maj_stat = gss_display_status(
|
||||
&min_stat,
|
||||
err_min,
|
||||
GSS_C_MECH_CODE,
|
||||
GSS_C_NULL_OID,
|
||||
&msg_ctx,
|
||||
&status_string
|
||||
);
|
||||
if (! GSS_ERROR(maj_stat)) {
|
||||
strncpy(buf_min, (char*) status_string.value, GSS_ERRBUF_SIZE);
|
||||
gss_release_buffer(&min_stat, &status_string);
|
||||
}
|
||||
} while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
|
||||
}
|
||||
|
||||
86
vendor/github.com/ubccr/kerby/kerberosgss.h
generated
vendored
Normal file
86
vendor/github.com/ubccr/kerby/kerberosgss.h
generated
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Adopted from PyKerberos. Modified for use with Kerby.
|
||||
*
|
||||
* Copyright (c) 2006-2015 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h> // for close
|
||||
#include <gssapi/gssapi.h>
|
||||
#include <gssapi/gssapi_generic.h>
|
||||
#include <gssapi/gssapi_krb5.h>
|
||||
|
||||
#define krb5_get_err_text(context,code) error_message(code)
|
||||
|
||||
#define AUTH_GSS_ERROR -1
|
||||
#define AUTH_GSS_COMPLETE 1
|
||||
#define AUTH_GSS_CONTINUE 0
|
||||
|
||||
#define GSS_AUTH_P_NONE 1
|
||||
#define GSS_AUTH_P_INTEGRITY 2
|
||||
#define GSS_AUTH_P_PRIVACY 4
|
||||
#define GSS_ERRBUF_SIZE 512
|
||||
|
||||
typedef struct {
|
||||
gss_ctx_id_t context;
|
||||
gss_name_t server_name;
|
||||
long int gss_flags;
|
||||
gss_cred_id_t client_creds;
|
||||
char* username;
|
||||
char* response;
|
||||
int responseConf;
|
||||
OM_uint32 maj_stat;
|
||||
OM_uint32 min_stat;
|
||||
} gss_client_state;
|
||||
|
||||
typedef struct {
|
||||
gss_ctx_id_t context;
|
||||
gss_name_t server_name;
|
||||
gss_name_t client_name;
|
||||
gss_cred_id_t server_creds;
|
||||
gss_cred_id_t client_creds;
|
||||
char* username;
|
||||
char* targetname;
|
||||
char* response;
|
||||
char* ccname;
|
||||
OM_uint32 maj_stat;
|
||||
OM_uint32 min_stat;
|
||||
} gss_server_state;
|
||||
|
||||
gss_client_state* new_gss_client_state();
|
||||
void free_gss_client_state(gss_client_state *state);
|
||||
gss_server_state* new_gss_server_state();
|
||||
void free_gss_server_state(gss_server_state *state);
|
||||
void get_gss_error(OM_uint32 err_maj, char *buf_maj, OM_uint32 err_min, char *buf_min);
|
||||
|
||||
int authenticate_gss_client_init(
|
||||
const char* service, const char* principal, long int gss_flags,
|
||||
gss_server_state* delegatestate, gss_client_state* state
|
||||
);
|
||||
int authenticate_gss_client_clean(
|
||||
gss_client_state *state
|
||||
);
|
||||
int authenticate_gss_client_step(
|
||||
gss_client_state *state, const char *challenge
|
||||
);
|
||||
int authenticate_gss_server_init(
|
||||
const char* service, gss_server_state* state
|
||||
);
|
||||
int authenticate_gss_server_clean(
|
||||
gss_server_state *state
|
||||
);
|
||||
int authenticate_gss_server_step(
|
||||
gss_server_state *state, const char *challenge
|
||||
);
|
||||
268
vendor/github.com/ubccr/kerby/kerby.go
generated
vendored
Normal file
268
vendor/github.com/ubccr/kerby/kerby.go
generated
vendored
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
// Copyright 2015 Andrew E. Bruno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package kerby is a cgo wrapper for Kerberos GSSAPI
|
||||
package kerby
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -std=gnu99
|
||||
#cgo LDFLAGS: -lgssapi_krb5 -lkrb5 -lk5crypto -lcom_err
|
||||
#include "kerberosgss.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Kerberos GSSAPI Client
|
||||
type KerbClient struct {
|
||||
state *C.gss_client_state
|
||||
}
|
||||
|
||||
// Kerberos GSSAPI Server
|
||||
type KerbServer struct {
|
||||
state *C.gss_server_state
|
||||
}
|
||||
|
||||
// Returns the last major/minor GSSAPI error messages
|
||||
func (kc KerbClient) GssError() error {
|
||||
bufMaj := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1))
|
||||
bufMin := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1))
|
||||
defer C.free(unsafe.Pointer(bufMaj))
|
||||
defer C.free(unsafe.Pointer(bufMin))
|
||||
|
||||
C.get_gss_error(kc.state.maj_stat, bufMaj, kc.state.min_stat, bufMin)
|
||||
return errors.New(C.GoString(bufMaj) + " - " + C.GoString(bufMin))
|
||||
}
|
||||
|
||||
// Initializes a context for Kerberos GSSAPI client-side authentication.
|
||||
// KerbClient.Clean must be called after this function returns succesfully to
|
||||
// dispose of the context once all GSSAPI operations are complete. srv is the
|
||||
// service principal in the form "type@fqdn". princ is the client principal in the
|
||||
// form "user@realm".
|
||||
func (kc *KerbClient) Init(srv, princ string) error {
|
||||
service := C.CString(srv)
|
||||
defer C.free(unsafe.Pointer(service))
|
||||
principal := C.CString(princ)
|
||||
defer C.free(unsafe.Pointer(principal))
|
||||
|
||||
var delegatestate *C.gss_server_state
|
||||
gss_flags := C.long(C.GSS_C_MUTUAL_FLAG | C.GSS_C_SEQUENCE_FLAG)
|
||||
result := 0
|
||||
|
||||
kc.state = C.new_gss_client_state()
|
||||
if kc.state == nil {
|
||||
return errors.New("Failed to allocate memory for gss_client_state")
|
||||
}
|
||||
|
||||
result = int(C.authenticate_gss_client_init(service, principal, gss_flags, delegatestate, kc.state))
|
||||
|
||||
if result == C.AUTH_GSS_ERROR {
|
||||
return kc.GssError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the client response from the last successful GSSAPI client-side step.
|
||||
func (kc *KerbClient) Response() string {
|
||||
return C.GoString(kc.state.response)
|
||||
}
|
||||
|
||||
// Processes a single GSSAPI client-side step using the supplied server data.
|
||||
func (kc *KerbClient) Step(chlg string) error {
|
||||
challenge := C.CString(chlg)
|
||||
defer C.free(unsafe.Pointer(challenge))
|
||||
result := 0
|
||||
|
||||
if kc.state == nil {
|
||||
return errors.New("Invalid client state")
|
||||
}
|
||||
|
||||
result = int(C.authenticate_gss_client_step(kc.state, challenge))
|
||||
|
||||
if result == C.AUTH_GSS_ERROR {
|
||||
return kc.GssError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroys the context for GSSAPI client-side authentication. After this call
|
||||
// the KerbClient.state object is invalid and should not be used again.
|
||||
func (kc *KerbClient) Clean() {
|
||||
if kc.state != nil {
|
||||
C.authenticate_gss_client_clean(kc.state)
|
||||
C.free_gss_client_state(kc.state)
|
||||
kc.state = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the service principal for the server given a service type and
|
||||
// hostname. Adopted from PyKerberos.
|
||||
func ServerPrincipalDetails(service, hostname string) (string, error) {
|
||||
var code C.krb5_error_code
|
||||
var kcontext C.krb5_context
|
||||
var kt C.krb5_keytab
|
||||
var cursor C.krb5_kt_cursor
|
||||
var entry C.krb5_keytab_entry
|
||||
var pname *C.char
|
||||
|
||||
match := fmt.Sprintf("%s/%s@", service, hostname)
|
||||
|
||||
code = C.krb5_init_context(&kcontext)
|
||||
if code != 0 {
|
||||
return "", fmt.Errorf("Cannot initialize Kerberos5 context: %d", code)
|
||||
}
|
||||
|
||||
code = C.krb5_kt_default(kcontext, &kt)
|
||||
if code != 0 {
|
||||
return "", fmt.Errorf("Cannot get default keytab: %d", int(code))
|
||||
}
|
||||
|
||||
code = C.krb5_kt_start_seq_get(kcontext, kt, &cursor)
|
||||
if code != 0 {
|
||||
return "", fmt.Errorf("Cannot get sequence cursor from keytab: %d", int(code))
|
||||
}
|
||||
|
||||
result := ""
|
||||
for {
|
||||
code = C.krb5_kt_next_entry(kcontext, kt, &entry, &cursor)
|
||||
if code != 0 {
|
||||
break
|
||||
}
|
||||
|
||||
code = C.krb5_unparse_name(kcontext, entry.principal, &pname)
|
||||
if code != 0 {
|
||||
return "", fmt.Errorf("Cannot parse principal name from keytab: %d", int(code))
|
||||
}
|
||||
|
||||
result = C.GoString(pname)
|
||||
if strings.HasPrefix(result, match) {
|
||||
C.krb5_free_unparsed_name(kcontext, pname)
|
||||
C.krb5_free_keytab_entry_contents(kcontext, &entry)
|
||||
break
|
||||
}
|
||||
|
||||
result = ""
|
||||
C.krb5_free_unparsed_name(kcontext, pname)
|
||||
C.krb5_free_keytab_entry_contents(kcontext, &entry)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return "", errors.New("Principal not found in keytab")
|
||||
}
|
||||
|
||||
if cursor != nil {
|
||||
C.krb5_kt_end_seq_get(kcontext, kt, &cursor)
|
||||
}
|
||||
|
||||
if kt != nil {
|
||||
C.krb5_kt_close(kcontext, kt)
|
||||
}
|
||||
|
||||
C.krb5_free_context(kcontext)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Returns the last major/minor GSSAPI error messages
|
||||
func (ks KerbServer) GssError() error {
|
||||
bufMaj := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1))
|
||||
bufMin := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1))
|
||||
defer C.free(unsafe.Pointer(bufMaj))
|
||||
defer C.free(unsafe.Pointer(bufMin))
|
||||
|
||||
C.get_gss_error(ks.state.maj_stat, bufMaj, ks.state.min_stat, bufMin)
|
||||
return errors.New(C.GoString(bufMaj) + " - " + C.GoString(bufMin))
|
||||
}
|
||||
|
||||
// Initializes a context for GSSAPI server-side authentication with the given
|
||||
// service principal. KerbServer.Clean must be called after this function
|
||||
// returns succesfully to dispose of the context once all GSSAPI operations are
|
||||
// complete. srv is the service principal in the form "type@fqdn".
|
||||
func (ks *KerbServer) Init(srv string) error {
|
||||
service := C.CString(srv)
|
||||
defer C.free(unsafe.Pointer(service))
|
||||
|
||||
result := 0
|
||||
|
||||
ks.state = C.new_gss_server_state()
|
||||
if ks.state == nil {
|
||||
return errors.New("Failed to allocate memory for gss_server_state")
|
||||
}
|
||||
|
||||
result = int(C.authenticate_gss_server_init(service, ks.state))
|
||||
|
||||
if result == C.AUTH_GSS_ERROR {
|
||||
return ks.GssError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the user name of the principal trying to authenticate to the server.
|
||||
// This method must only be called after KerbServer.Step returns a complete or
|
||||
// continue response code.
|
||||
func (ks *KerbServer) UserName() string {
|
||||
return C.GoString(ks.state.username)
|
||||
}
|
||||
|
||||
// Get the target name if the server did not supply its own credentials. This
|
||||
// method must only be called after KerbServer.Step returns a complete or
|
||||
// continue response code.
|
||||
func (ks *KerbServer) TargetName() string {
|
||||
return C.GoString(ks.state.targetname)
|
||||
}
|
||||
|
||||
// Get the server response from the last successful GSSAPI server-side step.
|
||||
func (ks *KerbServer) Response() string {
|
||||
return C.GoString(ks.state.response)
|
||||
}
|
||||
|
||||
// Processes a single GSSAPI server-side step using the supplied client data.
|
||||
func (ks *KerbServer) Step(chlg string) error {
|
||||
challenge := C.CString(chlg)
|
||||
defer C.free(unsafe.Pointer(challenge))
|
||||
result := 0
|
||||
|
||||
if ks.state == nil {
|
||||
return errors.New("Invalid client state")
|
||||
}
|
||||
|
||||
result = int(C.authenticate_gss_server_step(ks.state, challenge))
|
||||
|
||||
if result == C.AUTH_GSS_ERROR {
|
||||
return ks.GssError()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroys the context for GSSAPI server-side authentication. After this call
|
||||
// the KerbServer.state object is invalid and should not be used again.
|
||||
func (ks *KerbServer) Clean() {
|
||||
if ks.state != nil {
|
||||
C.authenticate_gss_server_clean(ks.state)
|
||||
C.free_gss_server_state(ks.state)
|
||||
ks.state = nil
|
||||
}
|
||||
}
|
||||
39
vendor/github.com/ubccr/kerby/khttp/handler.go
generated
vendored
Normal file
39
vendor/github.com/ubccr/kerby/khttp/handler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package khttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ubccr/kerby"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Handler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authReq := strings.Split(r.Header.Get(authorizationHeader), " ")
|
||||
if len(authReq) != 2 || authReq[0] != negotiateHeader {
|
||||
w.Header().Set(wwwAuthenticateHeader, negotiateHeader)
|
||||
http.Error(w, "Invalid authorization header", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
ks := new(kerby.KerbServer)
|
||||
err := ks.Init("")
|
||||
if err != nil {
|
||||
log.Printf("KerbServer Init Error: %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer ks.Clean()
|
||||
|
||||
err = ks.Step(authReq[1])
|
||||
if err != nil {
|
||||
log.Printf("KerbServer Step Error: %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(wwwAuthenticateHeader, fmt.Sprintf("%s %s", negotiateHeader, ks.Response()))
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
97
vendor/github.com/ubccr/kerby/khttp/http.go
generated
vendored
Normal file
97
vendor/github.com/ubccr/kerby/khttp/http.go
generated
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2015 Andrew E. Bruno
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package khttp is a transport that authenticates all outgoing requests using
|
||||
// SPNEGO (negotiate authentication) http://tools.ietf.org/html/rfc4559.
|
||||
package khttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ubccr/kerby"
|
||||
)
|
||||
|
||||
var (
|
||||
negotiateHeader = "Negotiate"
|
||||
wwwAuthenticateHeader = "WWW-Authenticate"
|
||||
authorizationHeader = "Authorization"
|
||||
)
|
||||
|
||||
// HTTP client transport that authenticates all outgoing
|
||||
// requests using SPNEGO. Implements the http.RoundTripper interface
|
||||
type Transport struct {
|
||||
// keytab file to use
|
||||
KeyTab string
|
||||
// principal
|
||||
Principal string
|
||||
// Next specifies the next transport to be used or http.DefaultTransport if nil.
|
||||
Next http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction performing SPNEGO negotiate
|
||||
// authentication.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if len(t.KeyTab) > 0 {
|
||||
os.Setenv("KRB5_CLIENT_KTNAME", t.KeyTab)
|
||||
}
|
||||
host, _, err := net.SplitHostPort(req.URL.Host)
|
||||
if err != nil {
|
||||
host = req.URL.Host
|
||||
}
|
||||
service := fmt.Sprintf("HTTP@%s", host)
|
||||
kc := new(kerby.KerbClient)
|
||||
err = kc.Init(service, t.Principal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer kc.Clean()
|
||||
|
||||
err = kc.Step("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set(authorizationHeader, negotiateHeader+" "+kc.Response())
|
||||
|
||||
tr := t.Next
|
||||
if tr == nil {
|
||||
tr = http.DefaultTransport
|
||||
if tr == nil {
|
||||
return nil, errors.New("khttp: no Next transport or DefaultTransport")
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := tr.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authReply := strings.Split(resp.Header.Get(wwwAuthenticateHeader), " ")
|
||||
if len(authReply) != 2 || strings.ToLower(authReply[0]) != strings.ToLower(negotiateHeader) {
|
||||
return nil, errors.New("khttp: server replied with invalid www-authenticate header")
|
||||
}
|
||||
|
||||
// Authenticate the reply from the server
|
||||
err = kc.Step(authReply[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
|
@ -130,6 +130,9 @@ github.com/pmezard/go-difflib/difflib
|
|||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/require
|
||||
github.com/stretchr/testify/suite
|
||||
# github.com/ubccr/kerby v0.0.0-20170626144437-201a958fc453
|
||||
github.com/ubccr/kerby
|
||||
github.com/ubccr/kerby/khttp
|
||||
# github.com/vmware/govmomi v0.23.0
|
||||
github.com/vmware/govmomi/find
|
||||
github.com/vmware/govmomi/govc/cli
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue