Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Authentication with Kerberos #63

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ package goph

import (
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"

"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"gopkg.in/jcmturner/gokrb5.v7/client"
"gopkg.in/jcmturner/gokrb5.v7/config"
"gopkg.in/jcmturner/gokrb5.v7/keytab"
)

// Auth represents ssh auth methods.
Expand Down Expand Up @@ -66,6 +70,44 @@ func RawKey(privateKey string, passphrase string) (Auth, error) {
}, nil
}

// KerberosWithPassword returns a kerberos auth with password.
func KerberosWithPassword(username, password, realm, target string, krb5cfg io.Reader) (Auth, error) {
cfg, err := config.NewConfigFromReader(krb5cfg)
if err != nil {
return nil, err
}

cl := client.NewClientWithPassword(username, realm, password, cfg, client.DisablePAFXFAST(true))
c, err := newKrb5Client(cl)
if err != nil {
return nil, err
}
return Auth{
ssh.GSSAPIWithMICAuthMethod(c, target),
}, nil
}

// KerberosWithKeytab returns a kerberos auth with keytab.
func KerberosWithKeytab(username, keytabFile, realm, target string, krb5cfg io.Reader) (Auth, error) {
kt, err := keytab.Load(keytabFile)
if err != nil {
return nil, err
}
cfg, err := config.NewConfigFromReader(krb5cfg)
if err != nil {
return nil, err
}

cl := client.NewClientWithKeytab(username, realm, kt, cfg, client.DisablePAFXFAST(true))
c, err := newKrb5Client(cl)
if err != nil {
return nil, err
}
return Auth{
ssh.GSSAPIWithMICAuthMethod(c, target),
}, nil
}

// HasAgent checks if ssh agent exists.
func HasAgent() bool {
return os.Getenv("SSH_AUTH_SOCK") != ""
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ module github.com/melbahja/goph
go 1.13

require (
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.5
golang.org/x/crypto v0.6.0
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/gokrb5.v7 v7.5.0
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down Expand Up @@ -49,5 +53,15 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
70 changes: 70 additions & 0 deletions krb5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2024 Mohammed El Bahja. All rights reserved.
// Use of this source code is governed by a MIT license.

package goph

import (
"strings"

"gopkg.in/jcmturner/gokrb5.v7/client"
"gopkg.in/jcmturner/gokrb5.v7/gssapi"
"gopkg.in/jcmturner/gokrb5.v7/spnego"
"gopkg.in/jcmturner/gokrb5.v7/types"
)

type krb5Client struct {
client *client.Client
skey types.EncryptionKey
gen bool
}

func newKrb5Client(c *client.Client) (*krb5Client, error) {
if err := c.Login(); err != nil {
return nil, err
}
return &krb5Client{client: c}, nil
}

func (c *krb5Client) InitSecContext(target string, token []byte, isGSSDelegCreds bool) (outputToken []byte, needContinue bool, err error) {
if c.gen {
return nil, false, nil
}

t := strings.Replace(target, "@", "/", 1)
tkt, skey, err := c.client.GetServiceTicket(t)
if err != nil {
return nil, false, err
}
c.skey = skey

gssApiFlags := []int{gssapi.ContextFlagInteg, gssapi.ContextFlagMutual}
if isGSSDelegCreds {
gssApiFlags = append(gssApiFlags, gssapi.ContextFlagDeleg)
}

krb5Tkn, err := spnego.NewKRB5TokenAPREQ(c.client, tkt, skey, gssApiFlags, nil)
if err != nil {
return nil, false, err
}

outputToken, err = krb5Tkn.Marshal()
if err != nil {
return nil, false, err
}
c.gen = true

return outputToken, true, nil
}

func (c *krb5Client) GetMIC(micFiled []byte) ([]byte, error) {
micTkn, err := gssapi.NewInitiatorMICToken(micFiled, c.skey)
if err != nil {
return nil, err
}
return micTkn.Marshal()
}

func (c *krb5Client) DeleteSecContext() error {
c.client.Destroy()
return nil
}