AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / server / 问题 / 1172420
Accepted
starfry
starfry
Asked: 2025-02-07 22:27:51 +0800 CST2025-02-07 22:27:51 +0800 CST 2025-02-07 22:27:51 +0800 CST

MIT Kerberos 幂等 Keytab 合并

  • 772

我正在尝试为 Linux (MIT kerberos) 编写一个幂等脚本,该脚本/etc/krb5.keytab通过与现有内容合并来应用给定的密钥表。在 MacOS(我相信它使用 Heimdal)上,这很容易:

ktutil copy /tmp/ktnew /etc/krb5.keytab

如果密钥/tmp/ktnew已经存在/etc/krb5.keytab那么它就不会改变(这可以通过前后哈希来确认)。

MIT 版本的ktutil似乎只能以交互方式工作,并且没有与 等效的版本copy。使用rkt和wkt会附加(因此会重复)而不是合并,因此不是幂等的。

是否可以使用 Linux 系统上常见的 MIT 工具以幂等方式(非交互方式)执行此操作?

kerberos
  • 1 1 个回答
  • 52 Views

1 个回答

  • Voted
  1. Best Answer
    grawity
    2025-02-08T00:46:25+08:002025-02-08T00:46:25+08:00

    据我所知,不是。MIT Krb5 还附带了k5srvutil脚本,该脚本依赖于kadmin非交互式ktrem子命令来删除被取代的密钥(那些 kvno 早于最新的密钥),但这就是它的全部功能。

    我认为合并密钥表首先就不是正确的做法——与其把所有内容都放在“机器”密钥表中(因此必须授予所有服务对密钥表的访问权限),你最多应该在其中放置host/*和nfs/*,而其他所有内容都应该使用不同的密钥表文件。(不支持本机指定密钥表的服务通常会KRB5_KTNAME=通过环境支持。)

    这样,keytab 的部署就会变得像覆盖整个文件一样简单。

    在我自己的项目(特别是在尝试通过 kadmin 幂等请求密钥的工具中ktadd)中,我通过 Python 解析密钥表以确定它是否已经具有即将添加的主体的密钥。这是一个起点:

    # binary_io.py (not the tidiest class, merely something I'd been carrying
    # around in various different projects over the years)
    import io
    import struct
    
    class StreamWrapper():
        def __init__(self, fh=b""):
            if isinstance(fh, bytes) or isinstance(fh, bytearray):
                self.fh = io.BytesIO(fh)
            elif hasattr(fh, "makefile"):
                self.fh = fh.makefile("rwb")
            else:
                self.fh = fh
    
        def seek(self, pos, whence=0):
            return self.fh.seek(pos, whence)
    
        def tell(self):
            return self.fh.tell()
    
        def read(self, length):
            buf = self.fh.read(length)
            if len(buf) < length:
                if len(buf) == 0:
                    raise EOFError("Hit EOF after %d/%d bytes" % (len(buf), length))
                else:
                    raise IOError("Hit EOF after %d/%d bytes" % (len(buf), length))
            return buf
    
        def write(self, buf):
            return self.fh.write(buf)
    
        def flush(self):
            return self.fh.flush()
    
    class BinaryReader(StreamWrapper):
        def _read_fmt(self, length, fmt):
            data, = struct.unpack(fmt, self.read(length))
            return data
    
        def read_u8(self):
            return self._read_fmt(1, "B")
    
        def read_u16_le(self):
            return self._read_fmt(2, "<H")
    
        def read_u16_be(self):
            return self._read_fmt(2, ">H")
    
        def read_u32_le(self):
            return self._read_fmt(4, "<L")
    
        def read_u32_be(self):
            return self._read_fmt(4, ">L")
    
        def read_u64_le(self):
            return self._read_fmt(8, "<Q")
    
        def read_u64_be(self):
            return self._read_fmt(8, ">Q")
    
    class BinaryWriter(StreamWrapper):
        def _write_fmt(self, fmt, *args):
            return self.write(struct.pack(fmt, *args))
    
        def write_u8(self, val):
            return self._write_fmt("B", val)
    
        def write_u16_le(self, val):
            return self._write_fmt("<H", val)
    
        def write_u16_be(self, val):
            return self._write_fmt(">H", val)
    
        def write_u32_le(self, val):
            return self._write_fmt("<L", val)
    
        def write_u32_be(self, val):
            return self._write_fmt(">L", val)
    
        def write_u64_le(self, val):
            return self._write_fmt("<Q", val)
    
        def write_u64_be(self, val):
            return self._write_fmt(">Q", val)
    
    class BinaryStream(BinaryReader, BinaryWriter):
        pass
    
    # -----
    # kerberos.py
    
    from dataclasses import dataclass
    
    # Format documentation:
    #   https://web.mit.edu/kerberos/krb5-latest/doc/formats/keytab_file_format.html
    #   https://www.gnu.org/software/shishi/manual/html_node/The-Keytab-Binary-File-Format.html
    
    # The default name type
    KRB5_NT_PRINCIPAL = 1
    
    @dataclass
    class Principal:
        nametype: int
        components: list[bytes]
        realm: bytes
    
        def unparse(self):
            # Mostly but not 100% correct
            sz = [s.replace(b"/", b"\\/") for s in self.components]
            sz = b"@".join([b"/".join(self.components), self.realm])
            return sz.decode()
    
    @dataclass
    class KeytabEntry:
        principal: Principal
        timestamp: int
        kvno: int
        enctype: int
        keydata: bytes
    
    class KrbBinaryReader(BinaryReader):
        # Turns out *nothing* is common between the two formats. Principals have
        # their name_type in a different place. Even 'data' has a 32-bit length in
        # ccache and 16-bit length in keytab.
    
        uses_native_endian = True
    
        def read_u16(self):
            if self.uses_native_endian:
                return self._read_fmt(2, "=H")
            else:
                return self.read_u16_be()
    
        def read_u32(self):
            if self.uses_native_endian:
                return self._read_fmt(4, "=L")
            else:
                return self.read_u32_be()
    
        def read_s32(self):
            if self.uses_native_endian:
                return self._read_fmt(4, "=l")
            else:
                return self._read_fmt(4, ">l")
    
        def read_time(self):
            return self.read_u32()
    
        def tell_eof(self):
            start_pos = self.tell()
            self.seek(0, 2)
            end_pos = self.tell()
            self.seek(start_pos)
            return end_pos
    
    class KeytabReader(KrbBinaryReader):
        version = None
    
        def read_data(self):
            length = self.read_u16()
            value = self.read(length)
            return value
    
        def read_principal(self):
            n_components = self.read_u16()
            if self.version == 1:
                n_components -= 1
            realm = self.read_data()
            components = []
            for i in range(n_components):
                components.append(self.read_data())
            name_type = KRB5_NT_PRINCIPAL
            if self.version >= 2:
                name_type = self.read_u32()
            return Principal(name_type, components, realm)
    
        def read_entry(self, size):
            end_pos = self.tell() + size
            principal = self.read_principal()
            timestamp = self.read_time()
            kvno = self.read_u8()
            enctype = self.read_u16()
            keydata = self.read_data()
            if end_pos - self.tell() >= 4:
                kvno = self.read_u32()
            # Skip to end of slot
            self.seek(end_pos)
            return KeytabEntry(principal, timestamp, kvno, enctype, keydata)
    
        def read_keytab(self):
            major = self.read_u8()
            if major != 5:
                raise IOError("Keytab major version not recognized")
            minor = self.read_u8()
            if not (1 <= minor <= 2):
                raise IOError("Keytab minor version not recognized")
            self.version = minor
            self.uses_native_endian = (self.version == 1)
            end_pos = self.tell_eof()
            while self.tell() < end_pos:
                length = self.read_s32()
                if length > 0:
                    yield self.read_entry(length)
                elif length < 0:
                    # Skip an empty slot
                    self.read(-length)
                else:
                    break
    
    # ---
    
    class Keytab():
        def __init__(self, path):
            self.path = path
    
        def list_principals(self):
            with open(self.path, "rb") as fh:
                rd = KeytabReader(fh)
                for e in rd.read_keytab():
                    principal = e.principal.unparse()
                    yield principal.rsplit("@", 1)[0]
    
        def has_principal(self, principal):
            if not os.path.exists(self.path):
                return False
            return principal in {*self.list_principals()}
    
    • 0

相关问题

  • Windows Admin Center 基于资源的委派停止使用 KRB_AP_ERR_MODIFIED 错误

  • MAC 10.4.11 & Win2k8

  • 有没有办法让 Kerberos 凭证委托两次?为什么不?

  • Windows 可以与 LDAP 集成吗?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    新安装后 postgres 的默认超级用户用户名/密码是什么?

    • 5 个回答
  • Marko Smith

    SFTP 使用什么端口?

    • 6 个回答
  • Marko Smith

    命令行列出 Windows Active Directory 组中的用户?

    • 9 个回答
  • Marko Smith

    什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同?

    • 3 个回答
  • Marko Smith

    如何确定bash变量是否为空?

    • 15 个回答
  • Martin Hope
    Tom Feiner 如何按大小对 du -h 输出进行排序 2009-02-26 05:42:42 +0800 CST
  • Martin Hope
    Noah Goodrich 什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同? 2009-05-19 18:24:42 +0800 CST
  • Martin Hope
    Brent 如何确定bash变量是否为空? 2009-05-13 09:54:48 +0800 CST
  • Martin Hope
    cletus 您如何找到在 Windows 中打开文件的进程? 2009-05-01 16:47:16 +0800 CST

热门标签

linux nginx windows networking ubuntu domain-name-system amazon-web-services active-directory apache-2.4 ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve