LinOTP配置

1.setup LinOTP

参考:https://www.linotp.org/download.html ,http://www.linotp.org/doc/latest/part-installation/server-installation/rpm_install.html

LinOTP Server IP:172.16.4.11
FreeRadius Server IP:172.16.4.11

 先安装各组件:

yum install epel-release.noarch
yum localinstall http://linotp.org/rpm/el7/linotp/x86_64/Packages/LinOTP_repos-1.1-1.el7.x86_64.rpm
yum update
yum install mariadb-server
yum install LinOTP
yum install LinOTP_mariadb
yum install yum-plugin-versionlock
yum versionlock python-repoze-who
/usr/bin/linotp-create-mariadb    #中间需要设置db root密码
yum install LinOTP_apache

systemctl enable httpd.service
systemctl status httpd.service

mkdir /etc/httpd/conf.d/old
mv /etc/httpd/conf.d/* /etc/httpd/conf.d/old
cp /etc/httpd/conf.d/old/ssl_linotp.conf.template /etc/httpd/conf.d/ssl_linotp.conf
systemctl start httpd.service

 

安装完成后访问管理控制台地址:

https://172.16.4.11/manage/     默认账号密码admin/Tio1LApw

修改admin默认密码:

htdigest /etc/linotp2/admins "LinOTP2 admin area" admin

用户访问自助服务地址:

https://172.16.4.11/selfservice/

用户令牌认证测试地址:

https://172.16.41.11/auth/index

 

登录管理控制台https://172.16.4.11/manage/ 

1.LDAP Resolver创建:

基本DN:不能包含中文,如果OU包含中文,只能写"dc=xx,dc=com",然后通过搜索过滤器以组的形式过滤有效用户

搜索过滤器:

(sAMAccountName=*)(objectClass=user) (!(userAccountControl:1.2.840.113556.1.4.803:=2))(memberOf:1.2.840.113556.1.4.1941:=cn=LinOTPGroup,cn=users,dc=51talk,dc=com) #包含LinOTPGroup组包含的用户及下面嵌套的组成员,用户状态为enabled

(sAMAccountName=*)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=cn=LinOTPGroup,cn=users,dc=xx,dc=com) #包含LinOTPGroup组包含的用户及下面嵌套的组成员

(sAMAccountName=*)(objectClass=user)(memberOf=cn=LinOTPGroup,cn=users,dc=xx,dc=com) #只包含LinOTPGroup组包含的组成员,不含嵌套组成员

属性映射:

{ "username": "sAMAccountName", "phone" : "telephoneNumber", "mobile" : "mobile", "email" : "mail", "surname" : "displayName", "givenname" : "department" }  #Key值不可更改,映射的字段属性可修改,注意大小写以Python-ldap查询为准

2.新建域,指向上一步创建的LDAP Resolver

3.新建策略:

1.手机端生成的Token显示为 liuy@xx.com

 

2.Token认证时,ptppin=0,认证时密码输入PIN+Token,PIN未设置则为空;如果otppin=1,则认证密码使用ldap password+OTP

认证策略具体参考:https://www.linotp.org/doc/latest/part-management/policy/authentication.html#policy-ki-qr-offline-token

 

#使用token验证用户登录是否正确:
浏览器访问:https://172.16.4.11/validate/check?user=liuy&pass=978874 (认证策略:otppin=0,PIN+Token,PIN未设置则为空;如果otppin=1,使用ldap password+OTP)

curl -k "https://172.16.4.11/validate/check?user=liuy&pass=721543"
curl -k "https://172.16.4.11/validate/simplecheck?user=liuy&pass=838355"

 

 

2.setup freeradius

参考:https://github.com/ZocStorm/LinOTP-and-FreeRadius ,https://github.com/mark-vandenbos/linotp-freeradius3-centos7

安装freeradius各组件,版本信息如下:

freeradius.x86_64                      3.0.13-15.el7
freeradius-perl.x86_64                 3.0.13-15.el7
freeradius-utils.x86_64                3.0.13-15.el7
perl-Config-IniFiles.noarch            2.79-1.el7
perl-Try-Tiny.noarch                   0.12-2.el7
perl-LWP-Protocol-https.noarch         6.04-4.el7

 

yum install freeradius
yum install freeradius-perl
yum install freeradius-utils
yum install perl-Try-Tiny.noarch
yum install perl-LWP-Protocol-https.noarch
yum install perl-Config-IniFiles

 

安装完成后执行如下命令:

cp -a /etc/raddb/ /etc/raddb.old

rm /etc/raddb/{clients.conf,users}
cp clients.conf /etc/raddb/clients.conf

clients.conf文件内容如下:cat /etc/raddb/clients.conf:

client vpn {
        ipaddr  = 172.16.24.1 #IP of the Radius client
        netmask = 32
        secret  = 'SECRETTEST' #shared secret, the client has to provide
}

client sshtest {
        ipaddr  = 172.16.4.12 #IP of the client
        netmask = 32
        secret  = 'SECRETTEST' #shared secret, the client has to provide
}

 注:配置完成后,如果修改clients.conf文件,则需要重启radius服务 systemctl restart radiusd,如果使用的调试模式,则停止调试模式重新启动即可,期间不要启动radius服务。

cp users /etc/raddb/users

users文件内容如下:cat /etc/raddb/users:

DEFAULT Auth-Type := perl

 

cp perl /etc/raddb/mods-available/perl
ln -s /etc/raddb/mods-available/perl /etc/raddb/mods-enabled/perl

perl文件内容如下:cat /etc/raddb/mods-available/perl:

perl {
        filename = /usr/lib/linotp/radius_linotp.pm
}

 

cp linotp /etc/raddb/sites-available/default
ln -s /etc/raddb/sites-available/default /etc/raddb/sites-enabled/default

default文件内容如下:cat /etc/raddb/sites-available/default:

server linotp {
        listen {
                ipaddr = *
                port = 0
                type = auth
        }

        authorize {
                preprocess
                IPASS
                suffix
                ntdomain
                files
                expiration
                logintime
                pap
                update control {
                      Auth-Type := Perl
                }
        }

        authenticate {
        Auth-Type Perl {
                perl
          }
        }
}

 

cp radius_linotp.pm /usr/lib/linotp/radius_linotp.pm

radius_linotp.pm文件内容如下:cat /usr/lib/linotp/radius_linotp.pm:

#
#    linotp-auth-freeradius-perl - LinOTP FreeRADIUS module for rlm_perl
#    Copyright (C) 2010 - 2017 KeyIdentity GmbH
#
#    Based on the example code for rlm_perl based FreeRADIUS plugins:
#    Copyright 2002  The FreeRADIUS server project
#    Copyright 2002  Boian Jordanov <bjordanov@orbitel.bg>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 2 of the License, or
#    (at your option) any later version.
#
#    This program 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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#    E-mail: linotp@keyidentity.com
#    Contact: www.linotp.org
#    Support: www.keyidentity.com


use strict;
#use IO::Socket::SSL qw(debug3); # <- enable SSL debugging!
use LWP 5.64;
use Config::IniFiles;
use Data::Dumper;
use vendor_perl::HTTP::Tiny;
use Try::Tiny;

# use ...
# This is very important ! Without this script will not get the filled  hashesh from main.
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK %RAD_CONFIG );

# This is hash wich hold original request from radius
#my %RAD_REQUEST;
# In this hash you add values that will be returned to NAS.
#my %RAD_REPLY;
#This is for check items
#my %RAD_CHECK;


# constant definition for the remapping of return values
use constant RLM_MODULE_REJECT  =>  0; #  /* immediately reject the request */
use constant RLM_MODULE_FAIL    =>  1; #  /* module failed, don't reply */
use constant RLM_MODULE_OK      =>  2; #  /* the module is OK, continue */
use constant RLM_MODULE_HANDLED =>  3; #  /* the module handled the request, so stop. */
use constant RLM_MODULE_INVALID =>  4; #  /* the module considers the request invalid. */
use constant RLM_MODULE_USERLOCK => 5; #  /* reject the request (user is locked out) */
use constant RLM_MODULE_NOTFOUND => 6; #  /* user not found */
use constant RLM_MODULE_NOOP     => 7; #  /* module succeeded without doing anything */
use constant RLM_MODULE_UPDATED  => 8; #  /* OK (pairs modified) */
use constant RLM_MODULE_NUMCODES => 9; #  /* How many return codes there are */

our $ret_hash = {
    0 => "RLM_MODULE_REJECT",
    1 => "RLM_MODULE_FAIL",
    2 => "RLM_MODULE_OK",
    3 => "RLM_MODULE_HANDLED",
    4 => "RLM_MODULE_INVALID",
    5 => "RLM_MODULE_USERLOCK",
    6 => "RLM_MODULE_NOTFOUND",
    7 => "RLM_MODULE_NOOP",
    8 => "RLM_MODULE_UPDATED",
    9 => "RLM_MODULE_NUMCODES"
};

## constant definition for comparison
use constant false => 0;
use constant true  => 1;

## constant definitions for logging
use constant Debug => 1;
use constant Auth  => 2;
use constant Info  => 3;
use constant Error => 4;
use constant Proxy => 5;
use constant Acct  => 6;

my $LIN_OK     = ":-)";
my $LIN_REJECT = ":-(";
my $LIN_FAIL   = ":-/";

# Read our Config
our $CONFIG_FILE = "/etc/linotp2/rlm_perl.ini";
our $Config = {};
our $cfg_file;

if ( -e $CONFIG_FILE ) {
    $cfg_file = Config::IniFiles->new( -file => $CONFIG_FILE);
    $Config->{FSTAT}      = "found!";
    $Config->{URL}        = $cfg_file->val("Default", "URL");
    $Config->{REALM}      = $cfg_file->val("Default", "REALM");
    $Config->{RESCONF}    = $cfg_file->val("Default", "RESCONF");
    $Config->{Debug}      = $cfg_file->val("Default", "DEBUG");
    $Config->{SSL_CHECK}  = $cfg_file->val("Default", "SSL_CHECK");
} else {
    $Config->{FSTAT}     = "not found!";
    $Config->{URL}       = 'https://localhost/validate/simplecheck';
    $Config->{REALM}     = '';
    $Config->{RESCONF}   = "";
    $Config->{Debug}     = "FALSE";
    $Config->{SSL_CHECK} = "FALSE";
}

# Function to handle authenticate
sub authenticate {

    ## show where the config comes from -
    # in the module init we can't print this out, so it starts here
    &radiusd::radlog( Info, "Config File $CONFIG_FILE $Config->{FSTAT}" );

    # we inherrit the defaults
    my $URL     = $Config->{URL};
    my $REALM   = $Config->{REALM};
    my $RESCONF = $Config->{RESCONF};

    # Ssl support...
    my $cafile = $Config->{HTTPS_CA_FILE} or ""; # <- ca certificate file
    my $capath = $Config->{HTTPS_CA_DIR}  or ""; # <- ca certificate dir
    my $chkssl = true; # <- for security reasons, chkssl is true by default
    if ( $Config->{SSL_CHECK} =~ /^\s*false\s*$/i ) {
        $chkssl = false;
    }

    my $useNasIdentifier = true;
    if ( $Config->{PREFER_NAS_IDENTIFIER} =~ /^\s*false\s*$/i ) {
        $useNasIdentifier = false;
    }

    my $debug = false;
    if ( $Config->{Debug} =~ /^\s*true\s*$/i ) {
        $debug = true;
    }

    &radiusd::radlog( Info, "Default URL $URL " );

    # if there exists an auth-type config may overwrite this
    my $auth_type = $RAD_CONFIG{"Auth-Type"};

    try {
        if ( exists( $Config->{$auth_type}{URL} ) ) {
            $URL = $Config->{$auth_type}{URL};
        }
        if ( exists( $Config->{$auth_type}{REALM} ) ) {
            $REALM = $Config->{$auth_type}{REALM};
        }
        if ( exists( $Config->{$auth_type}{RESCONF} ) ) {
            $RESCONF = $Config->{$auth_type}{RESCONF};
        }
    } catch {
        &radiusd::radlog( Error, "error: $@" );
    };

    if ( $debug == true ) {
        &log_request_attributes;
    }

    my %params = ();

    # put RAD_REQUEST members in the LinOTP request
    if ( exists( $RAD_REQUEST{'State'} ) ) {
        my $hexState = $RAD_REQUEST{'State'};
        if ( substr( $hexState, 0, 2 ) eq "0x" ) {
            $hexState = substr( $hexState, 2 );
        }
        $params{'state'} = pack 'H*', $hexState;
    }

    # Username and password...
    if ( exists( $RAD_REQUEST{'User-Name'} ) ) {
        $params{"user"} = $RAD_REQUEST{'User-Name'};
    }
    if ( exists( $RAD_REQUEST{'User-Password'} ) ) {
        $params{"pass"} = $RAD_REQUEST{'User-Password'};
    }

    # IP Address of client...
    if      ( $useNasIdentifier and exists( $RAD_REQUEST{'NAS-IP-Address'} ) ) {
        $params{"client"} = $RAD_REQUEST{'NAS-IP-Address'};
    } elsif ( $useNasIdentifier and exists( $RAD_REQUEST{'NAS-IPv6-Address'} ) ) {
        $params{"client"} = $RAD_REQUEST{'NAS-IPv6-Address'};
    } elsif ( exists( $RAD_REQUEST{'Packet-Src-IP-Address'} ) ) {
        $params{"client"} = $RAD_REQUEST{'Packet-Src-IP-Address'};
    } elsif ( exists( $RAD_REQUEST{'Packet-Src-IPv6-Address'} ) ) {
        $params{"client"} = $RAD_REQUEST{'Packet-Src-IPv6-Address'};
    } else {
        &radiusd::radlog( Info, "Warning, PACKET_SRC_IP_ADDRESS not available" );
    }

    if ( length($REALM) > 0 ) {
        $params{"realm"} = $REALM;
    }
    if ( length($RESCONF) > 0 ) {
        $params{"resConf"} = $RESCONF;
    }

    &radiusd::radlog( Info, "Auth-Type: $auth_type" );
    &radiusd::radlog( Info, "Url: $URL" );
    &radiusd::radlog( Info, "User: $RAD_REQUEST{'User-Name'}" );
    if ( $debug == true ) {
        &radiusd::radlog( Debug, "urlparam $_ = $params{$_}\n" )
            for ( keys %params );
    }
    else {
        &radiusd::radlog( Info, "urlparam $_ \n" )
            for ( keys %params );
    }

    my $ua = LWP::UserAgent->new();
    if ($chkssl == false) {
        $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00);
    } else {
        $ua->ssl_opts(verify_hostname => 1);
        if (length $cafile) {
            if ( $debug == true ) {
                &radiusd::radlog( Info, "ssl_opts(SSL_ca_file => '$cafile')" );
            }
            $ua->ssl_opts(SSL_ca_file => $cafile);
        }
        if (length $capath) {
            if ( $debug == true ) {
                &radiusd::radlog( Info, "ssl_opts(SSL_ca_path => '$capath')" );
            }
            $ua->ssl_opts(SSL_ca_path => $capath);
        }
    }
    my $response = $ua->post( $URL, \%params );
    if (not $response->is_success) {
        &radiusd::radlog( Info, "LinOTP Request failed: at $URL\nDetails: " . $response->status_line );
        $RAD_REPLY{'Reply-Message'} = "LinOTP server is not available!";
        return RLM_MODULE_FAIL;
    }

    my $content  = $response->decoded_content();
    if ( $debug == true ) {
        &radiusd::radlog( Debug, "Content $content" );
    }
    $RAD_REPLY{'Reply-Message'} = "LinOTP server denied access!";
    my $g_return = RLM_MODULE_REJECT;

    if ( $content eq $LIN_OK ) {
        &radiusd::radlog( Info, "LinOTP access granted" );
        $RAD_REPLY{'Reply-Message'} = "LinOTP access granted";
        $g_return = RLM_MODULE_OK;
    }
    elsif ( $content eq $LIN_FAIL ) {
        &radiusd::radlog( Info, "LinOTP access failed" );
        $RAD_REPLY{'Reply-Message'} = "LinOTP access failed";
        $g_return = RLM_MODULE_FAIL;
    }
    elsif ( $content eq $LIN_FAIL ) {
        &radiusd::radlog( Info, "LinOTP server denied access!" );
        $RAD_REPLY{'Reply-Message'} = "LinOTP server denied access!";
        $g_return = RLM_MODULE_REJECT;
    }
    elsif (( substr( $content, 0, length($LIN_REJECT) ) eq $LIN_REJECT )
        && ( length($content) > length($LIN_REJECT) ) )
    {
        ## we are in challenge response mode:
        ## 1. split the response in fail, state and challenge
        ## 2. show the client the challenge and the state
        ## 3. get the response and
        ## 4. submit the response and the state to linotp and
        ## 5. reply ok or reject

        &radiusd::radlog( Info, "Challenge Mode:" );
        my ( $ok, $state, $challenge ) = split( / +/, $content, 3 );
        if ( length($challenge) == 0 ) { $challenge = ""; }

        $RAD_REPLY{'State'}                = $state;
        $RAD_REPLY{'Reply-Message'}        = $challenge;
        $RAD_CHECK{'Response-Packet-Type'} = "Access-Challenge";
        $g_return                          = RLM_MODULE_HANDLED;
    }

    &radiusd::radlog( Info, "return $ret_hash->{$g_return}" );
    return $g_return;

}

sub log_request_attributes {

    #for ( keys %ENV ) {
    #    &radiusd::radlog( Debug, "ENV_VARIABLE: $_ = $ENV{$_}" );
    #    ;
    #}

    #for ( keys %RAD_CONFIG ) {
    #    &radiusd::radlog( Debug, "RAD_CONFIG: $_ = $RAD_CONFIG{$_}" );
    #    ;
    #}

    # This shouldn't be done in production environments!
    # This is only meant for debugging!
    for ( keys %RAD_REQUEST ) {
        &radiusd::radlog( Debug, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}" );
        ;
    }

}

# Function to handle authorize
sub authorize {

    # For debugging purposes only
    # &log_request_attributes;

    return RLM_MODULE_OK;
}

# Function to handle preacct
sub preacct {

    # For debugging purposes only
    #       &log_request_attributes;

    return RLM_MODULE_OK;
}

# Function to handle accounting
sub accounting {

    # For debugging purposes only
    #       &log_request_attributes;

    # You can call another subroutine from here
    &test_call;

    return RLM_MODULE_OK;
}

# Function to handle checksimul
sub checksimul {

    # For debugging purposes only
    #       &log_request_attributes;

    return RLM_MODULE_OK;
}

# Function to handle pre_proxy
sub pre_proxy {

    # For debugging purposes only
    #       &log_request_attributes;

    return RLM_MODULE_OK;
}

# Function to handle post_proxy
sub post_proxy {

    # For debugging purposes only
    #       &log_request_attributes;

    return RLM_MODULE_OK;
}

# Function to handle post_auth
sub post_auth {

    # For debugging purposes only
    #       &log_request_attributes;

    return RLM_MODULE_OK;
}

# Function to handle xlat
sub xlat {

    # For debugging purposes only
    #       &log_request_attributes;

    # Loads some external perl and evaluate it
    my ( $filename, $a, $b, $c, $d ) = @_;
    &radiusd::radlog( 1, "From xlat $filename " );
    &radiusd::radlog( 1, "From xlat $a $b $c $d " );
    local *FH;
    open FH, $filename or die "open '$filename' $!";
    local ($/) = undef;
    my $sub = <FH>;
    close FH;
    my $eval = qq{ sub handler{ $sub;} };
    eval $eval;
    eval { main->handler; };
}

# Function to handle detach
sub detach {

    # For debugging purposes only
    #       &log_request_attributes;

    # Do some logging.
    &radiusd::radlog( 0, "rlm_perl::Detaching. Reloading. Done." );
}

#
# Some functions that can be called from other functions
#

sub test_call {

    # Some code goes here
}

1;

 

cp rlm_perl.ini /etc/linotp2/rlm_perl.ini

rlm_perl.ini文件内容如下,按实际修改:cat /etc/linotp2/rlm_perl.ini:

[Default]
#IP of the linotp server
URL=https://172.16.4.11/validate/simplecheck
#optional: limits search for user to this realm
REALM=xx.com
#optional: only use this UserIdResolver
#RESCONF=flat_file
#optional: comment out if everything seems to work fine
Debug=True
#optional: use this, if you have selfsigned certificates, otherwise comment out
SSL_CHECK=False
rm /etc/raddb/sites-enabled/{default,inner-tunnel}
rm /etc/raddb/mods-enabled/eap

 

3.Radius验证

以上全部配置完成后,启动Radius调试模式:radiusd -X

检查占用1812进程的服务  lsof -i :1812
修改client.conf文件后需要重启radiusd服务:systemctl start radiusd

 

在Radius客户端服务器上安装 yum install freeradius-utils

然后通过以下命令测试Radius+LinOTP是否配置成功:

radtest liuy 104459 172.16.4.11 0 SECRET

  Radius Server端也会有相应的成功、失败提示信息

 

4.启用LinOTP审计


1.进入mysql,创建数据库,相应用户,赋予用户权限

create database LinOTPAudit; 
create user otpaudit@'localhost'identified by 'dbpassword';  #保留单引号
grant all privileges on LinOTPAudit.* to otpaudit@'localhost' identified by 'dbpassword';

 

2.创建私钥和公钥

cd /etc/linotp2
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

 

3.修改/etc/linotp2/linotp.ini,如下:

linotpAudit.type = linotp.lib.audit.SQLAudit
linotpAudit.sql.url = mysql://otpaudit:dbpassword@localhost/LinOTPAudit   #mysql://user:userpassword@localhost/dbname

linotpAudit.key.private = /etc/linotp2/private.pem
linotpAudit.key.public = /etc/linotp2/public.pem

4.重启linotp服务:systemctl restart httpd

5.打开管理控制台,审计日志已生成

 

 

 

 

 ======================================================================

使用radtest测试,无需再通过radexample进行验证。

FreeRadius client安装测试:

参考:https://www.cnblogs.com/minseo/p/14541355.html

yum install git
git clone https://github.com/FreeRADIUS/freeradius-client.git
yum install gcc

./configure
make
make install clean

 

修改配置文件 /etc/ld.so.conf,增加如下内容:
include /etc/ld.so.conf.d/*.conf /usr/local/lib
使配置生效:
/sbin/ldconfig
修改配置文件 /usr/local/etc/radiusclient,增加如下内容:
172.16.4.11 SECRETTEST
修改配置文件 /usr/local/etc/radiusclient/radiusclient.conf增加一行:
authserver 172.16.4.11

然后进行验证:radexample
输入radius server端配置的用户名和密码即可验证成功

 

LINOTP数据库备份与还原:

LinOTP全部配置好,测试认证都正常后,对当前服务器进行克隆备份保存,然后每天对所有数据库进行备份,如果服务器出问题,可在备机上还原LINOTP数据库,还原完成后用户Token可以正常使用

#指定备份服务器上的LINOTP、mysql两个数据库
mysqldump  -uroot -pPASSWORD --databases LINOTP mysql >/backup/linotp_backup.sql

#备份服务器上的所有数据库
mysqldump  -uroot -pPASSWORD --all-databases >/backup/linotp_backup_all.sql

#还原名为LINOTP的数据库
mysql  -uroot -pPASSWORD LINOTP < linotp_backup_20210701.sql

 mysql数据库备份还原参考 https://blog.csdn.net/plfplc/article/details/80704018

posted on 2021-06-22 15:24  momingliu11  阅读(1212)  评论(0编辑  收藏  举报