samba-tool: Use authentication file to pass credentials
authorNikola Radovanovic <nikoladsp@gmail.com>
Fri, 30 Sep 2022 07:38:12 +0000 (09:38 +0200)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 4 Oct 2022 02:48:37 +0000 (02:48 +0000)
In order not to pass credentials in clear-text directly over command line, this is a patch to store username/password/domain in a file and use it during domain join for example.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15031

Signed-off-by: Nikola Radovanovic <radovanovic.extern@univention.de>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
docs-xml/manpages/samba-tool.8.xml
python/samba/getopt.py
python/samba/tests/cred_opt.py

index 60f3a5f06ed48b8ce56c6655ae275e516fa949ee..965b9d6357ced91c0e84e49a91e38550633c3dc0 100644 (file)
@@ -62,6 +62,8 @@
 
        &cmdline.common.credentials.usekrb5ccache;
 
+       &cmdline.common.credentials.authenticationfile;
+
        <varlistentry>
        <term>--ipaddress=IPADDRESS</term>
        <listitem><para>
index 9e35273381f9fac7632987d3469e7caedd21e27a..ff8aead3f8d849dd22f87114fe22df4ef4d70513 100644 (file)
@@ -192,6 +192,10 @@ class CredentialsOptions(optparse.OptionGroup):
                          action="callback", type=str,
                          help="Kerberos Credentials cache",
                          callback=self._set_krb5_ccache)
+        self._add_option("-A", "--authentication-file", metavar="AUTHFILE",
+                         action="callback", type=str,
+                         help="Authentication file",
+                         callback=self._set_auth_file)
 
         # LEGACY
         self._add_option("-k", "--kerberos", metavar="KERBEROS",
@@ -293,6 +297,12 @@ class CredentialsOptions(optparse.OptionGroup):
         self.creds.set_kerberos_state(MUST_USE_KERBEROS)
         self.creds.set_named_ccache(arg)
 
+    def _set_auth_file(self, option, opt_str, arg, parser):
+        if os.path.exists(arg):
+            self.creds.parse_file(arg)
+            self.ask_for_password = False
+            self.machine_pass = False
+
     def get_credentials(self, lp, fallback_machine=False):
         """Obtain the credentials set on the command-line.
 
index 91ca68085b7ad17766d420d8cb479c71b22d6c7d..82c6434d9c839ddbf523c4f62e692c18373cfb8f 100644 (file)
@@ -20,6 +20,8 @@
 """
 
 import optparse
+import os
+from contextlib import contextmanager
 from samba.getopt import CredentialsOptions
 import samba.tests
 import setproctitle
@@ -28,6 +30,19 @@ import sys
 password_opt = '--password=super_secret_password'
 clear_password_opt = '--password=xxx'
 
+@contextmanager
+def auth_fle_opt(auth_file_path, long_opt=True):
+    old_argv = list(sys.argv)
+    try:
+        if long_opt:
+            sys.argv.append('--authentication-file=%s' % auth_file_path)
+        else:
+            sys.argv.append('-A')
+            sys.argv.append(auth_file_path)
+        yield
+    finally:
+        sys.argv = old_argv
+
 class CredentialsOptionsTests(samba.tests.TestCase):
 
     def setUp(self):
@@ -48,3 +63,87 @@ class CredentialsOptionsTests(samba.tests.TestCase):
         super(samba.tests.TestCase, self).tearDown()
         setproctitle.setproctitle(self.old_proctitle)
         sys.argv.pop()
+
+class AuthenticationFileTests(samba.tests.TestCaseInTempDir):
+
+    def setUp(self):
+        super(AuthenticationFileTests, self).setUp()
+
+        self.parser = optparse.OptionParser()
+        self.credopts = CredentialsOptions(self.parser)
+        self.parser.add_option_group(self.credopts)
+
+        self.auth_file_name = os.path.join(self.tempdir, 'auth.txt')
+
+        self.realm = 'realm.example.com'
+        self.domain = 'dom'
+        self.password = 'pass'
+        self.username = 'user'
+
+        auth_file_fd = open(self.auth_file_name, 'x')
+        auth_file_fd.write('realm=%s\n' % self.realm)
+        auth_file_fd.write('domain=%s\n' % self.domain)
+        auth_file_fd.write('username=%s\n' % self.username)
+        auth_file_fd.write('password=%s\n' % self.password)
+        auth_file_fd.close()
+
+    def tearDown(self):
+        super(AuthenticationFileTests, self).tearDown()
+
+        os.unlink(self.auth_file_name)
+
+    def test_long_option_valid_path(self):
+        with auth_fle_opt(self.auth_file_name):
+            self.parser.parse_args()
+            credopts = self.credopts
+            creds = credopts.creds
+
+            self.assertFalse(credopts.ask_for_password)
+            self.assertFalse(credopts.machine_pass)
+
+            self.assertEqual(self.username, creds.get_username())
+            self.assertEqual(self.password, creds.get_password())
+            self.assertEqual(self.domain.upper(), creds.get_domain())
+            self.assertEqual(self.realm.upper(), creds.get_realm())
+
+    def test_long_option_invalid_path(self):
+        with auth_fle_opt(self.auth_file_name + '.dontexist'):
+            self.parser.parse_args()
+            credopts = self.credopts
+            creds = credopts.creds
+
+            self.assertTrue(credopts.ask_for_password)
+            self.assertFalse(credopts.machine_pass)
+
+            self.assertIsNone(creds.get_username())
+            self.assertIsNone(creds.get_password())
+            self.assertIsNone(creds.get_domain())
+            self.assertIsNone(creds.get_realm())
+
+    def test_short_option_valid_path(self):
+        with auth_fle_opt(self.auth_file_name, long_opt=False):
+            self.parser.parse_args()
+            credopts = self.credopts
+            creds = credopts.creds
+
+            self.assertFalse(credopts.ask_for_password)
+            self.assertFalse(credopts.machine_pass)
+
+            self.assertEqual(self.username, creds.get_username())
+            self.assertEqual(self.password, creds.get_password())
+            self.assertEqual(self.domain.upper(), creds.get_domain())
+            self.assertEqual(self.realm.upper(), creds.get_realm())
+
+    def test_short_option_invalid_path(self):
+        with auth_fle_opt(self.auth_file_name + '.dontexist', long_opt=False):
+            self.parser.parse_args()
+            credopts = self.credopts
+            creds = credopts.creds
+
+            self.assertTrue(credopts.ask_for_password)
+            self.assertFalse(credopts.machine_pass)
+
+            self.assertIsNone(creds.get_username())
+            self.assertIsNone(creds.get_password())
+            self.assertIsNone(creds.get_domain())
+            self.assertIsNone(creds.get_realm())