mirror of
https://github.com/rclone/rclone
synced 2025-02-18 14:11:27 +01:00
parent
ec5489e23f
commit
2fd4c45b34
backend/smb
fstest
@ -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
78
backend/smb/kerberos.go
Normal 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)
|
||||
}
|
@ -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"`
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
74
fstest/testserver/init.d/TestSMBKerberos
Executable file
74
fstest/testserver/init.d/TestSMBKerberos
Executable 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
|
Loading…
Reference in New Issue
Block a user