go-hep.org/x/hep@v0.38.1/xrootd/xrdproto/auth/krb5/krb5.go (about)

     1  // Copyright ©2018 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package krb5 contains the implementation of krb5 (Kerberos) security provider.
     6  package krb5 // import "go-hep.org/x/hep/xrootd/xrdproto/auth/krb5"
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/jcmturner/gokrb5/v8/client"
    14  	"github.com/jcmturner/gokrb5/v8/config"
    15  	"github.com/jcmturner/gokrb5/v8/credentials"
    16  	"github.com/jcmturner/gokrb5/v8/crypto"
    17  	"github.com/jcmturner/gokrb5/v8/messages"
    18  	"github.com/jcmturner/gokrb5/v8/types"
    19  	"go-hep.org/x/hep/xrootd/xrdproto/auth"
    20  )
    21  
    22  // Default is a Kerberos 5 client configured from cached credentials.
    23  // If the credentials could not be correctly configured, Default will be nil.
    24  var Default auth.Auther
    25  
    26  func init() {
    27  	v, err := WithCredCache()
    28  	if err == nil {
    29  		Default = v
    30  	}
    31  }
    32  
    33  // Auth implements krb5 (Kerberos) security provider.
    34  type Auth struct {
    35  	client *client.Client
    36  }
    37  
    38  // WithPassword creates a new Auth configured from the provided user, realm and password.
    39  func WithPassword(user, realm, password string) (*Auth, error) {
    40  	cfg, err := config.Load(configPath)
    41  	if err != nil {
    42  		return nil, fmt.Errorf("auth/krb5: could not load kerberos-5 configuration: %w", err)
    43  	}
    44  
    45  	krb := client.NewWithPassword(user, realm, password, cfg)
    46  
    47  	err = krb.Login()
    48  	if err != nil {
    49  		return nil, fmt.Errorf("auth/krb5: could not login: %w", err)
    50  	}
    51  
    52  	return &Auth{client: krb}, nil
    53  }
    54  
    55  // WithCredCache creates a new Auth configured from cached credentials.
    56  func WithCredCache() (*Auth, error) {
    57  	cfg, err := config.Load(configPath)
    58  	if err != nil {
    59  		switch err.(type) {
    60  		case config.UnsupportedDirective:
    61  			// ok. just ignore it.
    62  		default:
    63  			return nil, fmt.Errorf("auth/krb5: could not load kerberos-5 configuration: %w", err)
    64  		}
    65  	}
    66  
    67  	cred, err := credentials.LoadCCache(cachePath())
    68  	if err != nil {
    69  		return nil, fmt.Errorf("auth/krb5: could not load kerberos-5 cached credentials: %w", err)
    70  	}
    71  
    72  	krb, err := client.NewFromCCache(cred, cfg)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("auth/krb5: could not create kerberos-5 client from cached credentials: %w", err)
    75  	}
    76  
    77  	return &Auth{client: krb}, nil
    78  }
    79  
    80  // WithClient creates a new Auth using the provided krb5 client.
    81  func WithClient(client *client.Client) *Auth {
    82  	return &Auth{client: client}
    83  
    84  }
    85  
    86  // Provider implements auth.Auther
    87  func (*Auth) Provider() string {
    88  	return "krb5"
    89  }
    90  
    91  // Type indicates that krb5 (Kerberos) authentication protocol is used.
    92  var Type = [4]byte{'k', 'r', 'b', '5'}
    93  
    94  // Request implements auth.Auther
    95  func (a *Auth) Request(params []string) (*auth.Request, error) {
    96  	if len(params) == 0 {
    97  		return nil, errors.New("auth/krb5: want at least 1 parameter, got 0")
    98  	}
    99  	serviceName := string(params[0])
   100  	if strings.Contains(serviceName, "@") {
   101  		// Service name from the XRootD server may be in the following format: "xrootd/server.example.com@example.com"
   102  		// While gokrb5 expects server name in that format: "xrootd/server.example.com".
   103  		// The "@example.com" part (realm) will be guessed from the instance name "server.example.com".
   104  		index := strings.Index(serviceName, "@")
   105  		serviceName = serviceName[:index]
   106  	}
   107  	tkt, key, err := a.client.GetServiceTicket(serviceName)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("auth/krb5: could not retrieve kerberos service ticket: %w", err)
   110  	}
   111  	authenticator, err := types.NewAuthenticator(a.client.Credentials.Realm(), a.client.Credentials.CName())
   112  	if err != nil {
   113  		return nil, fmt.Errorf("auth/krb5: could not create kerberos authenticator: %w", err)
   114  	}
   115  	etype, err := crypto.GetEtype(key.KeyType)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("auth/krb5: could not retrieve crypto key type: %w", err)
   118  	}
   119  	err = authenticator.GenerateSeqNumberAndSubKey(key.KeyType, etype.GetKeyByteSize())
   120  	if err != nil {
   121  		return nil, fmt.Errorf("auth/krb5: could not generate sequence number or sub key: %w", err)
   122  	}
   123  	APReq, err := messages.NewAPReq(tkt, key, authenticator)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("auth/krb5: could not generate AP request: %w", err)
   126  	}
   127  	request, err := APReq.Marshal()
   128  	if err != nil {
   129  		return nil, fmt.Errorf("auth/krb5: could not marshal AP request: %w", err)
   130  	}
   131  
   132  	return &auth.Request{Type: Type, Credentials: "krb5\000" + string(request)}, nil
   133  }
   134  
   135  var (
   136  	_ auth.Auther = (*Auth)(nil)
   137  )