grpckrb
grpckrb provides the client and server interceptors required to implement Kerberos based authentication for GRPC.
These interceptors make use of the gokrb5 library.
Please refer to the documentation there for more information.
Server side configuration
- Load the service's keytab
- Create gokrb5 service settings
- Create a
grpckrb.KRBServerInterceptor
with the service settings
- Create a new GRPC server with the UnaryInterceptor and StreamInterceptor server options set.
import (
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/service"
)
kt, _ := keytab.Load("path/to/keytab/file")
si := &grpckrb.KRBServerInterceptor{
Settings: service.NewSettings(kt),
}
opts := []grpc.ServerOption{
grpc.UnaryInterceptor(si.Unary()),
grpc.StreamInterceptor(si.Stream()),
}
grpcSrv := grpc.NewServer(opts...)
Authorization
Without any authorization settings any valid authenticated user will have access to all GRPC methods.
A map of GRPC methods to authorising attributes can be added to the grpckrb.KRBServerInterceptor
object:
authzRoles := make(map[string][]string)
authzRoles["/Service/Reflector"] = []string{"testuser1@TEST.GOKRB5"}
authzRoles["/Service/Mirror"] = []string{"testuser1@TEST.GOKRB5"}
si := &grpckrb.KRBServerInterceptor{
Settings: service.NewSettings(kt),
AuthorizationRoles: authzRoles,
}
The authorising attributes can be user principal names or,
if Active Directory is being used as the KDC, SIDs of AD groups.
Anonymuos access
By default any GRPC methods that do not have any authorization settings in the map will be accessible to any valid authenticated user.
If desired they can be made accessible to anonymous users by setting the AllowAnonymous
field of the
grpckrb.KRBServerInterceptor
to true:
si := &grpckrb.KRBServerInterceptor{
Settings: service.NewSettings(kt),
AllowAnonymous: true,
}
Best Practices
Logging
It is recommended to implement a logger on the server side. This can be done through the gokrb5 service settings:
l := log.New(os.Stdout, "KRB Auth: ", log.LstdFlags)
si := &grpckrb.KRBServerInterceptor{
Settings: service.NewSettings(kt, service.Logger(l)),
}
Client side configuration
- Load the clients keytab
- Load the client krb5.conf
- Create a gokrb5 client
- Create a
grpckrb.KRBClientInterceptor
with the gokrb5 client
- Dial the GRPC connection with the
WithUnaryInterceptor
and WithStreamInterceptor
DialOptions set.
import (
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/keytab"
)
kcfg, _ := config.Load("path/to/krb5.conf")
kt, _ := keytab.Load("path/to/keytab/file")
cl := client.NewWithKeytab(username, "MY.REALM", kt, kcfg)
ci := &KRBClientInterceptor{
KRBClient: cl,
}
opts := []grpc.DialOption{
grpc.WithUnaryInterceptor(ci.Unary()),
grpc.WithStreamInterceptor(ci.Stream())}
conn, _ := grpc.Dial(addr, opts...)
Service Principal Name
In Kerberos authentication the client must request a ticket from the KDC for the service it wants to access.
The Service Principal Name (SPN) is what specifies this service.
The server side must have a keytab which contains the key for this SPN.
The SPN the client requests tickets for is derived in a number of ways:
-
If nothing is configured the SPN used will default to GRPC/<hostname>
where hostname
is the host portion of the address passed to the grpc.Dial
function.
-
This SPN can be overridden by setting the value of the DefaultSPN
field on the grpckrb.KRBClientInterceptor
object:
ci := &KRBClientInterceptor{
KRBClient: cl,
DefaultSPN: "HOST/host.example.com",
}
-
SPNs can be defined per GRPC method call using the map in the MethodSPNs
field of the grpckrb.KRBClientInterceptor
object:
spns := make(map[string]string)
spns["/Service/Reflector"] = "HOST/host-a.example.com"
spns["/Service/Mirror"] = "HOST/host-b.example.com"
ci := &KRBClientInterceptor{
KRBClient: cl,
MethodSPNs: spns,
}
Any method not in this map will fall back to using the DefaultSPN.