最新消息:

让 Net::SSH::Perl 从内存和程序本身读取 publickey

Perl admin 2860浏览 0评论

以前做跳板机的程序,都是使用的密码认证,因为这样好区分不同的权限。如果使用 key 的话,因为文件在本地,担心不安全容易复制出去,也没法控制用户只使用指定的跳板程序来做登陆的过程。
所以我们有个强列的需求,远程登陆只有指定的程序才能登陆,这样好做审核,所以系统的 ssh 命令不能登陆才对,key 不能让用户能见到才能包证其它程序不使用,当然也不能让用户支持密码登陆。
所以做一个正规一定的跳板机需要以上的条件。所以我们只有给 key 写到程序本身才能实现。但基本的 Net::SSH::Perl 这个模块只支持指定文件路径的 publickey 才行。我们必须修复这个程序本身。
我试着重载的方式不破坏程序本身来修改,发现根本不能实现,因为我在主程序中修改完后。后面的 Net::SSH::Perl 内部有多层的 require 会直接冲掉我的重载。还有回调也会使用当前的命名空间。所以这个方案失败了。只能使用修改模块,然后放自己的目标的方式来实现。

给 key 写到程序本身这个事情非常容易。我们使用 Perl 中强大的现成的 Data::Section::Simple 模块,就不在自己造轮子了。这个模块可以直接从 __DATA__ 中读取指定的 @@ 开关命名的字段.

use Data::Section::Simple qw(get_data_section);
my $rootkey = get_data_section('rootkey');
 
__DATA__
 
@@ rootkey
-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEA1QjVtiYPs5ONAZc6XGYc37zrsqnU8OR5Gs7TNwTo1fzomMKk
7j/HS0f64eC72/LjIxGWQZABwhnKf1W2VD017C3fNDniCDHaksGGXEL+7oZ5Ae6f

接下来我自己造了一个 Net::SSH::Perl 中的配置关键字 identity_mem_keys 来给指定的 key 的内容传进去.

use lib 'lib';
my $rootkey = get_data_section('rootkey');
my $ssh = Net::SSH::Perl->new('xxx.xxx.xxx.xxx',
        port => 22,
        privileged => 0,
        identity_mem_keys => [ $rootkey],
        debug => 1
);
$ssh->login;
my ($stdout, $stderr, $exit) = $ssh->cmd("ls -l /tmp");
print "$stdoutn";

关于程序本身,我就 hook 了一个文件 Net::SSH::Perl::Auth::PublicKey 中的二个部分,替换了原生的的 authenticate 加入了可以从内存中取 key 的功能。

sub authenticate {
    my $auth = shift;
    my $ssh = $auth->{ssh};
 
    my $sent = 0;
    if (my $agent = $auth->mgr->agent) {
        do {
            $sent = $auth->_auth_agent;
        } until $sent || $agent->num_left <= 0;
    }
    return $sent if $sent;
 
    my $ik = $ssh->config->get('identity_mem_keys') || [];
    for my $key (@$ik) {
        return 1 if $auth->_auth_key($key);
    }
 
    my $if = $ssh->config->get('identity_files') || [];
    my $idx = $auth->{_identity_idx} || 0;
    for my $f (@$if[$idx..$#$if]) {
        $auth->{_identity_idx}++;
        return 1 if $auth->_auth_identity($f);
    }
 
};

这个新加的配置文件的关键字,就是在这个地方生效,自己定了一个 _auth_key 的函数。然后在这个函数中做了很复杂的读 key 之类的操作.

sub _auth_key {
    my $auth = shift;
    my($auth_data) = @_;
    my $ssh = $auth->{ssh};
 
    my($key);
    $ssh->debug("Trying pubkey authentication with memory key file ");
 
    no warnings qw(redefine);
    *Net::SSH::Perl::Key::read_mem_private_pem = sub {
        my $class = shift;
        my $desc = $_[0];
        return unless $desc;
        chomp($desc);
        my($object) = $desc =~ /^-----?s?BEGIN ([^n-]+)s?-?----/;
        $object =~ s/s*$//;
        my %OBJ_MAP = (
            'DSA PRIVATE KEY'  => [ 'DSA' ],
            'SSH2 ENCRYPTED PRIVATE KEY' => [ 'DSA', [ 'SSH2' ] ],
            'RSA PRIVATE KEY'  => [ 'RSA' ],
        );
        my $rec = $OBJ_MAP{$object} or return;
        $class = "Net::SSH::Perl::Key::" . $rec->[0];
        eval "use $class;";
        die "Key class '$class' is unsupported: $@" if $@;
        my @args = $rec->[1] ? @{ $rec->[1] } : ();
 
        # read_private
        my($key_file, $passphrase, $datafellows) = (@_, @args);
 
        my $key = $class->new(undef, $datafellows);
        my $pem = $key->_pem;
        my $pkey = $pem->decode(
            Content => $desc,
            Password => $passphrase,
        );
 
        return unless $pkey;
 
        for my $m (qw( n e )) {
            $key->{rsa_pub}->$m( $pkey->{RSAPrivateKey}->{$m} );
        }
        ## Don't use iqmp from the keyfile; let Crypt::RSA compute
        ## it on its own, because the parameters in Crypt::RSA CRT
        ## differ from those in OpenSSL, and we need to use ipmq,
        ## not iqmp.
        for my $m (qw( n d p q dp dq )) {
            $key->{rsa_priv}->$m( $pkey->{RSAPrivateKey}->{$m} );
        }
 
        return $key;
    };
    $key = Net::SSH::Perl::Key->read_mem_private_pem($auth_data, '', $ssh->{datafellows});
    if (!$key) {
        my $passphrase = "";
        if ($ssh->config->get('interactive')) {
            $passphrase = _read_passphrase("Enter passphrase for keyfile : ");
        }
        else {
            $ssh->debug("Will not query passphrase for  in batch mode.");
        }
 
        $key = Net::SSH::Perl::Key->read_mem_private_pem($auth_data, $passphrase, $ssh->{datafellows});
        if (!$key) {
            $ssh->debug("Loading private key failed.");
            return 0;
        }
    }
 
    $auth->_sign_send_pubkey($key, &key_sign);
 
};

在这个中我还重载了一下 Net::SSH::Perl::Key::read_mem_private_pem 这个函数,来支持从内存参数中读取 key 的功能。这样整个文件完成了。
以后只要连接的时候判断一下环境变量的用户是什么,然后根据用户所在组的不同,来读不同的 key ,然后连接就好了。这样整个过程都是保密的。。当然记得给你的程序过一下 filter 模块来加密一下。

以上前一个函数的修改参考于 Use in-memory file as argument in SFTP

转载请注明:爱开源 » 让 Net::SSH::Perl 从内存和程序本身读取 publickey

您必须 登录 才能发表评论!