#!/usr/bin/python3

# This file is part of Cockpit.
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import parent

from testlib import *


@skipImage("No realmd available", "fedora-coreos")
@skipImage("freeipa not currently in testing", "debian-testing", "ubuntu-2004")
@skipImage("Skip these for now, unsure what's broken", "ubuntu-stable", "debian-stable")
@skipDistroPackage()
@skipImage("Needs sssd >=2.4.1", "rhel-8-4", "rhel-8-5", "centos-8-stream")
class TestS4USsh(MachineCase):
    provision = {
        "0": {"address": "10.111.113.1/20", "dns": "10.111.112.100"},
        "1": {"address": "10.111.113.2/20", "dns": "10.111.112.100"},
        "services": {"image": "services", "memory_mb": 2048}
    }

    def setUp(self):
        super().setUp()
        self.machines['services'].execute("/root/run-freeipa")
        # during image creation the /var/cache directory gets cleaned up, recreate the krb5rcache
        self.machines['0'].execute("mkdir /var/cache/krb5rcache && restorecon /var/cache/krb5rcache")
        self.machines['1'].execute("mkdir /var/cache/krb5rcache && restorecon /var/cache/krb5rcache")

    def testBasic(self):
        client_machine = self.machine
        b = self.browser
        sshd_machine = self.machines["1"]
        ipa_machine = self.machines["services"]

        # set hostname for readable logs
        client_machine.execute("hostnamectl set-hostname sshclient.cockpit.lan")
        sshd_machine.execute("hostnamectl set-hostname sshserver.cockpit.lan")

        # join both machines
        wait(lambda: client_machine.execute("nslookup -type=SRV _ldap._tcp.cockpit.lan"))
        wait(lambda: sshd_machine.execute("nslookup -type=SRV _ldap._tcp.cockpit.lan"))
        sshd_machine.execute("echo foobarfoo | realm join -U admin cockpit.lan")
        # join client machine with Cockpit, to create the HTTP/ principal
        self.login_and_go("/system")
        b.click("#system_information_domain_button")
        b.wait_popup("realms-op")
        b.set_val("#realms-op-address", "cockpit.lan")
        b.wait_text(".realms-op-address-error", "Contacted domain")
        b.set_val("#realms-op-admin", "admin")
        b.set_val("#realms-op-admin-password", "foobarfoo")
        b.click(".realms-op-apply")
        with b.wait_timeout(300):
            b.wait_popdown("realms-op")

        # configure ipa
        script = """set -eu
        echo foobarfoo | kinit admin@COCKPIT.LAN

        # add user to impersonate
        ipa user-add --first=user --last=user user
        echo foobarfoo | ipa user-mod user --password

        # allow our cockpit/ws service to delegate creds; FIXME: do this by default, and document transition from existing ones
        ipa service-mod HTTP/sshclient.cockpit.lan@COCKPIT.LAN --ok-to-auth-as-delegate=true

        # set up delegation rule
        ipa servicedelegationtarget-add cockpit-target
        # FIXME: how to allow this to all machines in the network? https://pagure.io/freeipa/issue/8927
        ipa servicedelegationtarget-add-member cockpit-target --principals="host/sshserver.cockpit.lan@COCKPIT.LAN" --principals "host/sshclient.cockpit.lan@COCKPIT.LAN"
        ipa servicedelegationrule-add cockpit-delegation
        ipa servicedelegationrule-add-member cockpit-delegation --principals="HTTP/sshclient.cockpit.lan@COCKPIT.LAN"
        ipa servicedelegationrule-add-target cockpit-delegation --servicedelegationtargets="cockpit-target"
        """
        ipa_machine.execute("podman exec freeipa bash -ec '%s'" % script)

        # ssh client, get keytab for authenticating and make ccache using gssapi
        client_machine.write('/tmp/make-cache.py', """
import gssapi, pwd, os

user = 'user@COCKPIT.LAN'
ccache = f'/run/cockpit/tls/{user}.ccache'

principal = gssapi.Name('HTTP/sshclient.cockpit.lan@COCKPIT.LAN', gssapi.NameType.kerberos_principal)
credResult = gssapi.creds.rcred_cred_store.acquire_cred_from(store = { 'keytab'.encode(): '/etc/cockpit/krb5.keytab'.encode() }, name=principal)
impersonee = gssapi.Name(user, gssapi.NameType.kerberos_principal)
credResults4u2self = gssapi.creds.rcred_s4u.acquire_cred_impersonate_name(credResult.creds, impersonee)
gssapi.creds.rcred_cred_store.store_cred_into({ 'ccache'.encode(): f'FILE:{ccache}'.encode(), 'keytab'.encode(): '/etc/cockpit/krb5.keytab'.encode() }, credResults4u2self.creds, overwrite=True)
# make it readable for the user
u = pwd.getpwnam(user)
os.chmod(ccache, 0o400)
os.chown(ccache, u.pw_uid, -1)
""")
        client_machine.execute(script="""#!/bin/sh -eu
        echo foobarfoo | kinit admin@COCKPIT.LAN
        kinit -k -t /etc/cockpit/krb5.keytab HTTP/sshclient.cockpit.lan@COCKPIT.LAN
        python3 /tmp/make-cache.py
        """)

        # enable gssapiauth for ssh
        sshd_machine.execute("sed -ri 's/#GSSAPIAuthentication (yes|no)/GSSAPIAuthentication yes/' /etc/ssh/sshd_config")
        sshd_machine.execute("systemctl daemon-reload && systemctl restart sshd")
        out = client_machine.execute("su -c 'KRB5RCACHEDIR=/var/cache/krb5rcache KRB5_TRACE=/dev/stderr KRB5CCNAME=/run/cockpit/tls/user@COCKPIT.LAN.ccache ssh -vvv -K  user@sshserver.cockpit.lan echo hello' user")
        self.assertEqual(out.strip(), "hello")

        # enable PAM kerberos authentication for sudo; see man pam_sss_gss(8)
        for m in [client_machine, sshd_machine]:
            m.execute(script=r"""#!/bin/sh -eu
            sed -i '/\[domain\/cockpit.lan\]/ a pam_gssapi_services = sudo, sudo-i' /etc/sssd/sssd.conf
            sed -i '1 a auth sufficient pam_sss_gss.so' /etc/pam.d/sudo
            systemctl restart sssd
            usermod -G wheel user
            """)

        # direct sudo with the delegated ticket
        out = client_machine.execute('su -c "KRB5CCNAME=/run/cockpit/tls/user@COCKPIT.LAN.ccache sudo whoami" user')
        self.assertEqual(out.strip(), "root")

        # ssh -K is supposed to forward the credentials cache, but doesn't; klist in the ssh session is empty
        # and there is no ccache; so scp it manually; this emulates what cockpit-ssh will eventually do
        # (except that it would probably put the ticket into the default keyring)
        client_machine.execute("""su -c "KRB5_TRACE=/dev/stderr KRB5CCNAME=/run/cockpit/tls/user@COCKPIT.LAN.ccache scp /run/cockpit/tls/user@COCKPIT.LAN.ccache user@sshserver.cockpit.lan:cache" user """)

        # ssh via s4u gssapi + sudo with the forwarded delegated ticket
        out = client_machine.execute("""su -c "KRB5_TRACE=/dev/stderr KRB5CCNAME=/run/cockpit/tls/user@COCKPIT.LAN.ccache ssh -vvv -K  user@sshserver.cockpit.lan 'KRB5CCNAME=/home/user/cache sudo whoami' " user""")
        self.assertEqual(out.strip(), "root")


if __name__ == '__main__':
    test_main()
