1
mirror of https://github.com/rclone/rclone synced 2025-02-18 14:11:27 +01:00

smb: add support for kerberos authentication

Fixes 
This commit is contained in:
Jonathan Giannuzzi 2025-01-14 08:59:11 +00:00 committed by Nick Craig-Wood
parent ec5489e23f
commit 2fd4c45b34
7 changed files with 198 additions and 3 deletions

View File

@ -31,13 +31,29 @@ func (f *Fs) dial(ctx context.Context, network, addr string) (*conn, error) {
}
}
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
d := &smb2.Dialer{}
if f.opt.UseKerberos {
cl, err := getKerberosClient()
if err != nil {
return nil, err
}
spn := f.opt.SPN
if spn == "" {
spn = "cifs/" + f.opt.Host
}
d.Initiator = &smb2.Krb5Initiator{
Client: cl,
TargetSPN: spn,
}
} else {
d.Initiator = &smb2.NTLMInitiator{
User: f.opt.User,
Password: pass,
Domain: f.opt.Domain,
TargetSPN: f.opt.SPN,
},
}
}
session, err := d.DialConn(ctx, tconn, addr)

78
backend/smb/kerberos.go Normal file
View File

@ -0,0 +1,78 @@
package smb
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"sync"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/credentials"
)
var (
kerberosClient *client.Client
kerberosErr error
kerberosOnce sync.Once
)
// getKerberosClient returns a Kerberos client that can be used to authenticate.
func getKerberosClient() (*client.Client, error) {
if kerberosClient == nil || kerberosErr == nil {
kerberosOnce.Do(func() {
kerberosClient, kerberosErr = createKerberosClient()
})
}
return kerberosClient, kerberosErr
}
// createKerberosClient creates a new Kerberos client.
func createKerberosClient() (*client.Client, error) {
cfgPath := os.Getenv("KRB5_CONFIG")
if cfgPath == "" {
cfgPath = "/etc/krb5.conf"
}
cfg, err := config.Load(cfgPath)
if err != nil {
return nil, err
}
// Determine the ccache location from the environment, falling back to the
// default location.
ccachePath := os.Getenv("KRB5CCNAME")
switch {
case strings.Contains(ccachePath, ":"):
parts := strings.SplitN(ccachePath, ":", 2)
switch parts[0] {
case "FILE":
ccachePath = parts[1]
case "DIR":
primary, err := os.ReadFile(filepath.Join(parts[1], "primary"))
if err != nil {
return nil, err
}
ccachePath = filepath.Join(parts[1], strings.TrimSpace(string(primary)))
default:
return nil, fmt.Errorf("unsupported KRB5CCNAME: %s", ccachePath)
}
case ccachePath == "":
u, err := user.Current()
if err != nil {
return nil, err
}
ccachePath = "/tmp/krb5cc_" + u.Uid
}
ccache, err := credentials.LoadCCache(ccachePath)
if err != nil {
return nil, err
}
return client.NewFromCCache(ccache, cfg)
}

View File

@ -76,6 +76,16 @@ authentication, and it often needs to be set for clusters. For example:
Leave blank if not sure.
`,
Sensitive: true,
}, {
Name: "use_kerberos",
Help: `Use Kerberos authentication.
If set, rclone will use Kerberos authentication instead of NTLM. This
requires a valid Kerberos configuration and credentials cache to be
available, either in the default locations or as specified by the
KRB5_CONFIG and KRB5CCNAME environment variables.
`,
Default: false,
}, {
Name: "idle_timeout",
Default: fs.Duration(60 * time.Second),
@ -126,6 +136,7 @@ type Options struct {
Pass string `config:"pass"`
Domain string `config:"domain"`
SPN string `config:"spn"`
UseKerberos bool `config:"use_kerberos"`
HideSpecial bool `config:"hide_special_share"`
CaseInsensitive bool `config:"case_insensitive"`
IdleTimeout fs.Duration `config:"idle_timeout"`

View File

@ -2,6 +2,7 @@
package smb_test
import (
"path/filepath"
"testing"
"github.com/rclone/rclone/backend/smb"
@ -15,3 +16,13 @@ func TestIntegration(t *testing.T) {
NilObject: (*smb.Object)(nil),
})
}
func TestIntegration2(t *testing.T) {
krb5Dir := t.TempDir()
t.Setenv("KRB5_CONFIG", filepath.Join(krb5Dir, "krb5.conf"))
t.Setenv("KRB5CCNAME", filepath.Join(krb5Dir, "ccache"))
fstests.Run(t, &fstests.Opt{
RemoteName: "TestSMBKerberos:rclone",
NilObject: (*smb.Object)(nil),
})
}

View File

@ -484,6 +484,9 @@ backends:
- backend: "smb"
remote: "TestSMB:rclone"
fastlist: false
- backend: "smb"
remote: "TestSMBKerberos:rclone"
fastlist: false
- backend: "storj"
remote: "TestStorj:"
fastlist: true

View File

@ -26,6 +26,8 @@ They should be bound to localhost so they are not accessible externally.
| 28630 | TestSMB |
| 28631 | TestFTPProftpd |
| 28632 | TestSwiftAIOsegments |
| 28633 | TestSMBKerberos |
| 28634 | TestSMBKerberos |
| 38081 | TestWebdavOwncloud |
## Non localhost tests

View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -e
IMAGE=rclone/test-smb-kerberos
NAME=smb-kerberos
USER=rclone
DOMAIN=RCLONE
REALM=RCLONE.LOCAL
SMB_PORT=28633
KRB5_PORT=28634
. $(dirname "$0")/docker.bash
start() {
docker build -t ${IMAGE} - <<EOF
FROM alpine:3.21
RUN apk add --no-cache samba-dc
RUN rm -rf /etc/samba/smb.conf /var/lib/samba \
&& mkdir -p /var/lib/samba/private \
&& samba-tool domain provision \
--use-rfc2307 \
--option acl_xattr:security_acl_name=user.NTACL \
--realm=$REALM \
--domain=$DOMAIN \
--server-role=dc \
--dns-backend=SAMBA_INTERNAL \
--host-name=localhost \
&& samba-tool user add --random-password $USER \
&& mkdir -m 777 /share /rclone \
&& cat <<EOS >> /etc/samba/smb.conf
[public]
path = /share
browseable = yes
read only = yes
guest ok = yes
[rclone]
path = /rclone
browseable = yes
read only = no
guest ok = no
valid users = rclone
EOS
CMD ["samba", "-i"]
EOF
docker run --rm -d --name ${NAME} \
-p 127.0.0.1:${SMB_PORT}:445 \
-p 127.0.0.1:${SMB_PORT}:445/udp \
-p 127.0.0.1:${KRB5_PORT}:88 \
${IMAGE}
# KRB5_CONFIG and KRB5CCNAME are set by the caller
cat > ${KRB5_CONFIG} <<EOF
[libdefaults]
default_realm = ${REALM}
[realms]
${REALM} = {
kdc = localhost
}
EOF
docker cp ${KRB5_CONFIG} ${NAME}:/etc/krb5.conf
docker exec ${NAME} samba-tool user get-kerberos-ticket rclone --output-krb5-ccache=/tmp/ccache
docker cp ${NAME}:/tmp/ccache ${KRB5CCNAME}
sed -i -e "s/localhost/localhost:${KRB5_PORT}/" ${KRB5_CONFIG}
echo type=smb
echo host=localhost
echo port=$SMB_PORT
echo use_kerberos=true
echo _connect=127.0.0.1:${SMB_PORT}
}
. $(dirname "$0")/run.bash