Skip to content

Commit

Permalink
Merge 72a3709 into 19a3481
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Apr 17, 2021
2 parents 19a3481 + 72a3709 commit 6085ee5
Show file tree
Hide file tree
Showing 5 changed files with 10 additions and 97 deletions.
36 changes: 4 additions & 32 deletions docs/frontends/FTP-and-SFTP.rst
Expand Up @@ -7,11 +7,10 @@ Tahoe-LAFS SFTP Frontend
1. `SFTP Background`_
2. `Tahoe-LAFS Support`_
3. `Creating an Account File`_
4. `Running An Account Server (accounts.url)`_
5. `Configuring SFTP Access`_
6. `Dependencies`_
7. `Immutable and Mutable Files`_
8. `Known Issues`_
4. `Configuring SFTP Access`_
5. `Dependencies`_
6. `Immutable and Mutable Files`_
7. `Known Issues`_


SFTP Background
Expand Down Expand Up @@ -78,33 +77,6 @@ start with "ssh-".
Now add an ``accounts.file`` directive to your ``tahoe.cfg`` file, as described in
the next sections.

Running An Account Server (accounts.url)
========================================

The accounts.url directive allows access requests to be controlled by an
HTTP-based login service, useful for centralized deployments. This was used
by AllMyData to provide web-based file access, where the service used a
simple PHP script and database lookups to map an account email address and
password to a Tahoe-LAFS directory cap. The service will receive a
multipart/form-data POST, just like one created with a <form> and <input>
fields, with three parameters:

• action: "authenticate" (this is a static string)
• email: USERNAME (Tahoe-LAFS has no notion of email addresses, but the
authentication service uses them as account names, so the interface
presents this argument as "email" rather than "username").
• passwd: PASSWORD

It should return a single string that either contains a Tahoe-LAFS directory
cap (URI:DIR2:...), or "0" to indicate a login failure.

Tahoe-LAFS recommends the service be secure, preferably localhost-only. This
makes it harder for attackers to brute force the password or use DNS
poisoning to cause the Tahoe-LAFS gateway to talk with the wrong server,
thereby revealing the usernames and passwords.

Public key authentication is not supported when an account server is used.

Configuring SFTP Access
=======================

Expand Down
1 change: 1 addition & 0 deletions newsfragments/3652.removed
@@ -0,0 +1 @@
Removed support for the Account Server frontend authentication type.
4 changes: 1 addition & 3 deletions src/allmydata/client.py
Expand Up @@ -116,7 +116,6 @@ def _is_valid_section(section_name):
),
"sftpd": (
"accounts.file",
"accounts.url",
"enabled",
"host_privkey_file",
"host_pubkey_file",
Expand Down Expand Up @@ -1042,13 +1041,12 @@ def init_sftp_server(self):
accountfile = self.config.get_config("sftpd", "accounts.file", None)
if accountfile:
accountfile = self.config.get_config_path(accountfile)
accounturl = self.config.get_config("sftpd", "accounts.url", None)
sftp_portstr = self.config.get_config("sftpd", "port", "tcp:8022")
pubkey_file = self.config.get_config("sftpd", "host_pubkey_file")
privkey_file = self.config.get_config("sftpd", "host_privkey_file")

from allmydata.frontends import sftpd
s = sftpd.SFTPServer(self, accountfile, accounturl,
s = sftpd.SFTPServer(self, accountfile,
sftp_portstr, pubkey_file, privkey_file)
s.setServiceParent(self)

Expand Down
55 changes: 0 additions & 55 deletions src/allmydata/frontends/auth.py
@@ -1,14 +1,10 @@
import os

from zope.interface import implementer
from twisted.web.client import getPage
from twisted.internet import defer
from twisted.cred import error, checkers, credentials
from twisted.conch.ssh import keys
from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB

from allmydata.util.dictutil import BytesKeyDict
from allmydata.util import base32
from allmydata.util.fileutil import abspath_expanduser_unicode


Expand Down Expand Up @@ -86,54 +82,3 @@ def _checkPassword(self, creds):
d = defer.maybeDeferred(creds.checkPassword, correct)
d.addCallback(self._cbPasswordMatch, str(creds.username))
return d


@implementer(checkers.ICredentialsChecker)
class AccountURLChecker(object):
credentialInterfaces = (credentials.IUsernamePassword,)

def __init__(self, client, auth_url):
self.client = client
self.auth_url = auth_url

def _cbPasswordMatch(self, rootcap, username):
return FTPAvatarID(username, rootcap)

def post_form(self, username, password):
sepbase = base32.b2a(os.urandom(4))
sep = "--" + sepbase
form = []
form.append(sep)
fields = {"action": "authenticate",
"email": username,
"passwd": password,
}
for name, value in fields.iteritems():
form.append('Content-Disposition: form-data; name="%s"' % name)
form.append('')
assert isinstance(value, str)
form.append(value)
form.append(sep)
form[-1] += "--"
body = "\r\n".join(form) + "\r\n"
headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
}
return getPage(self.auth_url, method="POST",
postdata=body, headers=headers,
followRedirect=True, timeout=30)

def _parse_response(self, res):
rootcap = res.strip()
if rootcap == "0":
raise error.UnauthorizedLogin
return rootcap

def requestAvatarId(self, credentials):
# construct a POST to the login form. While this could theoretically
# be done with something like the stdlib 'email' package, I can't
# figure out how, so we just slam together a form manually.
d = self.post_form(credentials.username, credentials.password)
d.addCallback(self._parse_response)
d.addCallback(self._cbPasswordMatch, str(credentials.username))
return d

11 changes: 4 additions & 7 deletions src/allmydata/frontends/sftpd.py
Expand Up @@ -1983,7 +1983,7 @@ def closed(self):
components.registerAdapter(ShellSession, SFTPUserHandler, ISession)


from allmydata.frontends.auth import AccountURLChecker, AccountFileChecker, NeedRootcapLookupScheme
from allmydata.frontends.auth import AccountFileChecker, NeedRootcapLookupScheme

@implementer(portal.IRealm)
class Dispatcher(object):
Expand All @@ -2000,7 +2000,7 @@ def requestAvatar(self, avatarID, mind, interface):
class SFTPServer(service.MultiService):
name = "frontend:sftp"

def __init__(self, client, accountfile, accounturl,
def __init__(self, client, accountfile,
sftp_portstr, pubkey_file, privkey_file):
precondition(isinstance(accountfile, (str, type(None))), accountfile)
precondition(isinstance(pubkey_file, str), pubkey_file)
Expand All @@ -2013,12 +2013,9 @@ def __init__(self, client, accountfile, accounturl,
if accountfile:
c = AccountFileChecker(self, accountfile)
p.registerChecker(c)
if accounturl:
c = AccountURLChecker(self, accounturl)
p.registerChecker(c)
if not accountfile and not accounturl:
if not accountfile:
# we could leave this anonymous, with just the /uri/CAP form
raise NeedRootcapLookupScheme("must provide an account file or URL")
raise NeedRootcapLookupScheme("must provide an account file")

pubkey = keys.Key.fromFile(pubkey_file.encode(get_filesystem_encoding()))
privkey = keys.Key.fromFile(privkey_file.encode(get_filesystem_encoding()))
Expand Down

0 comments on commit 6085ee5

Please sign in to comment.