mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-10-06 07:47:08 +08:00
refactor: refactor ssh structure (#311)
This commit is contained in:
256
pkg/ssh/gssapi.go
Normal file
256
pkg/ssh/gssapi.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jcmturner/gokrb5/v8/client"
|
||||
"github.com/jcmturner/gokrb5/v8/config"
|
||||
"github.com/jcmturner/gokrb5/v8/credentials"
|
||||
"github.com/jcmturner/gokrb5/v8/crypto"
|
||||
"github.com/jcmturner/gokrb5/v8/gssapi"
|
||||
"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
|
||||
"github.com/jcmturner/gokrb5/v8/iana/flags"
|
||||
"github.com/jcmturner/gokrb5/v8/keytab"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/spnego"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
type Krb5ClientState int
|
||||
|
||||
const (
|
||||
ContextFlagREADY = 128
|
||||
/* initiator states */
|
||||
InitiatorStart Krb5ClientState = iota
|
||||
InitiatorRestart
|
||||
InitiatorWaitForMutal
|
||||
InitiatorReady
|
||||
)
|
||||
|
||||
func NewKrb5InitiatorClientWithPassword(username, password, krb5Conf string) (kcl Krb5InitiatorClient, err error) {
|
||||
c, err := config.Load(krb5Conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set to lookup KDCs in DNS
|
||||
c.LibDefaults.DNSLookupKDC = true
|
||||
c.LibDefaults.DNSLookupRealm = true
|
||||
|
||||
// Blank out the KDCs to ensure they are not being used
|
||||
c.Realms = []config.Realm{}
|
||||
|
||||
defaultRealm := c.LibDefaults.DefaultRealm
|
||||
|
||||
cl := client.NewWithPassword(username, defaultRealm, password, c)
|
||||
err = cl.Login()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = cl.AffirmLogin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return Krb5InitiatorClient{
|
||||
client: cl,
|
||||
state: InitiatorStart,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewKrb5InitiatorClientWithKeytab(username string, krb5Conf, keytabConf string) (kcl Krb5InitiatorClient, err error) {
|
||||
c, err := config.Load(krb5Conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Set to lookup KDCs in DNS
|
||||
c.LibDefaults.DNSLookupKDC = true
|
||||
c.LibDefaults.DNSLookupRealm = true
|
||||
|
||||
// Blank out the KDCs to ensure they are not being used
|
||||
c.Realms = []config.Realm{}
|
||||
|
||||
// Init keytab from conf
|
||||
cache, err := keytab.Load(keytabConf)
|
||||
if err != nil {
|
||||
return kcl, fmt.Errorf("unmarshal keytabConf failed: %w", err)
|
||||
}
|
||||
|
||||
defaultRealm := c.LibDefaults.DefaultRealm
|
||||
|
||||
cl := client.NewWithKeytab(username, defaultRealm, cache, c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = cl.Login()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = cl.AffirmLogin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return Krb5InitiatorClient{
|
||||
client: cl,
|
||||
state: InitiatorStart,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewKrb5InitiatorClientWithCache(krb5Conf, cacheFile string) (kcl Krb5InitiatorClient, err error) {
|
||||
c, err := config.Load(krb5Conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set to lookup KDCs in DNS
|
||||
c.LibDefaults.DNSLookupKDC = true
|
||||
c.LibDefaults.DNSLookupRealm = true
|
||||
|
||||
// Blank out the KDCs to ensure they are not being used
|
||||
c.Realms = []config.Realm{}
|
||||
|
||||
// Init krb5 client and login
|
||||
cache, err := credentials.LoadCCache(cacheFile)
|
||||
// https://stackoverflow.com/questions/58653482/what-is-the-default-kerberos-credential-cache-on-osx
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cl, err := client.NewFromCCache(cache, c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = cl.Login()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = cl.AffirmLogin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return Krb5InitiatorClient{
|
||||
client: cl,
|
||||
state: InitiatorStart,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Krb5InitiatorClient struct {
|
||||
state Krb5ClientState
|
||||
client *client.Client
|
||||
subkey types.EncryptionKey
|
||||
}
|
||||
|
||||
// Create new authenticator checksum for kerberos MechToken
|
||||
func (k *Krb5InitiatorClient) newAuthenticatorChksum(flags []int) []byte {
|
||||
a := make([]byte, 24)
|
||||
binary.LittleEndian.PutUint32(a[:4], 16)
|
||||
for _, i := range flags {
|
||||
if i == gssapi.ContextFlagDeleg {
|
||||
x := make([]byte, 28-len(a))
|
||||
a = append(a, x...)
|
||||
}
|
||||
f := binary.LittleEndian.Uint32(a[20:24])
|
||||
f |= uint32(i)
|
||||
binary.LittleEndian.PutUint32(a[20:24], f)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (k *Krb5InitiatorClient) InitSecContext(target string, token []byte, isGSSDelegCreds bool) ([]byte, bool, error) {
|
||||
GSSAPIFlags := []int{
|
||||
ContextFlagREADY,
|
||||
gssapi.ContextFlagInteg,
|
||||
gssapi.ContextFlagMutual,
|
||||
}
|
||||
if isGSSDelegCreds {
|
||||
GSSAPIFlags = append(GSSAPIFlags, gssapi.ContextFlagDeleg)
|
||||
}
|
||||
APOptions := []int{flags.APOptionMutualRequired}
|
||||
|
||||
switch k.state {
|
||||
case InitiatorStart, InitiatorRestart:
|
||||
newTarget := strings.ReplaceAll(target, "@", "/")
|
||||
|
||||
tkt, sessionKey, err := k.client.GetServiceTicket(newTarget)
|
||||
if err != nil {
|
||||
return []byte{}, false, err
|
||||
}
|
||||
|
||||
krb5Token, err := spnego.NewKRB5TokenAPREQ(k.client, tkt, sessionKey, GSSAPIFlags, APOptions)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error generating new kerberos 5 token: %w", err)
|
||||
}
|
||||
creds := k.client.Credentials
|
||||
auth, err := types.NewAuthenticator(creds.Domain(), creds.CName())
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error generating new authenticator: %w", err)
|
||||
}
|
||||
auth.Cksum = types.Checksum{
|
||||
CksumType: chksumtype.GSSAPI,
|
||||
Checksum: k.newAuthenticatorChksum(GSSAPIFlags),
|
||||
}
|
||||
etype, _ := crypto.GetEtype(sessionKey.KeyType)
|
||||
if err := auth.GenerateSeqNumberAndSubKey(sessionKey.KeyType, etype.GetKeyByteSize()); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
k.subkey = auth.SubKey
|
||||
|
||||
APReq, err := messages.NewAPReq(
|
||||
tkt,
|
||||
sessionKey,
|
||||
auth,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error generating NewAPReq: %w", err)
|
||||
}
|
||||
for _, o := range APOptions {
|
||||
types.SetFlag(&APReq.APOptions, o)
|
||||
}
|
||||
krb5Token.APReq = APReq
|
||||
|
||||
outToken, err := krb5Token.Marshal()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return []byte{}, false, err
|
||||
}
|
||||
k.state = InitiatorWaitForMutal
|
||||
return outToken, true, nil
|
||||
case InitiatorWaitForMutal:
|
||||
var krb5Token spnego.KRB5Token
|
||||
if err := krb5Token.Unmarshal(token); err != nil {
|
||||
err := fmt.Errorf("unmarshal APRep token failed: %w", err)
|
||||
return []byte{}, false, err
|
||||
}
|
||||
//var enc messages.EncAPRepPart
|
||||
//err2 := enc.Unmarshal(krb5Token.APRep.EncPart.Cipher)
|
||||
//fmt.Printf("err2: %#v, enc: %#v\n", err2, enc)
|
||||
|
||||
k.state = InitiatorReady
|
||||
return []byte{}, false, nil
|
||||
case InitiatorReady:
|
||||
return nil, false, fmt.Errorf("called one time too many, client has already been %d", k.state)
|
||||
default:
|
||||
return nil, false, fmt.Errorf("invalid state %d", k.state)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Krb5InitiatorClient) GetMIC(micFiled []byte) ([]byte, error) {
|
||||
micToken, err := gssapi.NewInitiatorMICToken(micFiled, k.subkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := micToken.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (k *Krb5InitiatorClient) DeleteSecContext() error {
|
||||
k.client.Destroy()
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user