JUnit中对Exception的判断

对于使用验证 Test Case 方法中抛出的异常,我起初想到的是一种比较简单的方法,但是显得比较繁琐:

    @Test
    public void testOldStyle() {
        try {
            double value = Math.random();
            if(value < 0.5) {
                throw new IllegalStateException("test");
            }
            Assert.fail("Expect IllegalStateException");
        } catch(IllegalStateException e) {
        }
    }

Google了一下,找到另外几种更加方便的方法:1,使用Test注解中的expected字段判断抛出异常的类型。2,使用ExpectedExceptionRule注解。
个人偏好用Test注解中的expected字段,它先的更加简洁,不管读起来还是写起来都很方便,并且一目了然:

    @Test(expected = IllegalStateException.class)
    public void testThrowException() {
        throw new IllegalStateException("test");
    }
    
    @Test(expected = IllegalStateException.class)
    public void testNotThrowException() {
        System.out.println("No Exception throws");
    }

Rule注解的使用(只有在JUnit4.7以后才有这个功能),它提供了更加强大的功能,它可以同时检查异常类型以及异常消息内容,这些内容可以只包含其中的某些字符,ExpectedException还支持使用hamcrest中的Matcher,默认使用IsInstanceOfStringContains Matcher。在BlockJUnit4ClassRunner的实现中,每一个Test Case运行时都会重新创建Test Class的实例,因而在使用ExpectedException这个Rule时,不用担心在多个Test Case之间相互影响的问题:

    @Rule
    public final ExpectedException expectedException = ExpectedException.none();
    
    @Test
    public void testThrowExceptionWithRule() {
        expectedException.expect(IllegalStateException.class);
        
        throw new IllegalStateException("test");
    }
    
    @Test
    public void testThrowExceptionAndMessageWithRule() {
        expectedException.expect(IllegalStateException.class);
        expectedException.expectMessage("fail");
        
        throw new IllegalStateException("expect fail");
    }

stackoverflow中还有人提到了使用google-code中的catch-exception工程,今天没时间看了,回去好好研究一下。

地址是:http://code.google.com/p/catch-exception/

参考链接


JUnit中对Exception的判断

PBKDF2加密的实现

PBKDF2(Password-Based Key Derivation Function)

通过哈希算法进行加密。由于哈希算法是单向的,能够将不论什么大小的数据转化为定长的“指纹”,并且无法被反向计算。

另外,即使数据源仅仅修改了一丁点。哈希的结果也会全然不同。

这种特性使得它很适合用于保存password。由于我们须要加密后的password无法被解密,同一时候也能保证正确校验每一个用户的password。可是哈希加密能够通过字典攻击和暴力攻击破解。

password加盐。盐是一个加入到用户的password哈希过程中的一段随机序列。

这个机制可以防止通过预先计算结果的彩虹表破解。每一个用户都有自己的盐,这种结果就是即使用户的password同样。通过加盐后哈希值也将不同。

为了校验password是否正确,我们须要储存盐值。通常和password哈希值一起存放在账户数据库中。或者直接存为哈希字符串的一部分。

import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class PasswordEncryption {

    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

    /**
     * 盐的长度
     */
    public static final int SALT_BYTE_SIZE = 32 / 2;

    /**
     * 生成密文的长度
     */
    public static final int HASH_BIT_SIZE = 128 * 4;

    /**
     * 迭代次数
     */
    public static final int PBKDF2_ITERATIONS = 1000;

    /**
     * 对输入的password进行验证
     *
     * @param attemptedPassword 待验证的password
     * @param encryptedPassword 密文
     * @param salt              盐值
     * @return 是否验证成功
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static boolean authenticate(String attemptedPassword, String encryptedPassword, String salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 用同样的盐值对用户输入的password进行加密
        String encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt);
        // 把加密后的密文和原密文进行比較,同样则验证成功。否则失败
        return encryptedAttemptedPassword.equals(encryptedPassword);
    }

    /**
     * 生成密文
     *
     * @param password 明文password
     * @param salt     盐值
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static String getEncryptedPassword(String password, String salt) throws NoSuchAlgorithmException,
            InvalidKeySpecException {

        KeySpec spec = new PBEKeySpec(password.toCharArray(), fromHex(salt), PBKDF2_ITERATIONS, HASH_BIT_SIZE);
        SecretKeyFactory f = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
        return toHex(f.generateSecret(spec).getEncoded());
    }

    /**
     * 通过提供加密的强随机数生成器 生成盐
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static String generateSalt() throws NoSuchAlgorithmException {
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[SALT_BYTE_SIZE];
        random.nextBytes(salt);

        return toHex(salt);
    }

    /**
     * 十六进制字符串转二进制字符串
     *
     * @param hex the hex string
     * @return the hex string decoded into a byte array
     */
    private static byte[] fromHex(String hex) {
        byte[] binary = new byte[hex.length() / 2];
        for (int i = 0; i < binary.length; i++) {
            binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
        return binary;
    }

    /**
     * 二进制字符串转十六进制字符串
     *
     * @param array the byte array to convert
     * @return a length*2 character string encoding the byte array
     */
    private static String toHex(byte[] array) {
        BigInteger bi = new BigInteger(1, array);
        String hex = bi.toString(16);
        int paddingLength = (array.length * 2) - hex.length();
        if (paddingLength > 0)
            return String.format("%0" + paddingLength + "d", 0) + hex;
        else
            return hex;
    }
}

首先要生成一个盐值salt,再把原始passwordsalt加密得到密文。验证的时候,把用户输入的password和同样的盐值salt使用同样的加密算法得到一个密文,将这个密文和原密文相比較,同样则验证通过,反之则不通过。

public static void main(String[] args) {
    String password = "test";
    String salt;
    String ciphertext;
    try {
        salt = PasswordEncryption.generateSalt();
        ciphertext = PasswordEncryption.getEncryptedPassword(password, salt);
        boolean result = PasswordEncryption.authenticate(password, ciphertext, salt);

        System.out.println(password + "  " + password.length());
        System.out.println(salt + "  " + salt.length());
        System.out.println(ciphertext + "  " + ciphertext.length());
        if (result) {
            System.out.println("succeed");
        } else {
            System.out.println("failed");
        }
    } catch (NoSuchAlgorithmException e) {
        System.out.println("NoSuchAlgorithmException");
    } catch (InvalidKeySpecException e) {
        System.out.println("InvalidKeySpecException");
    }
}

測试结果为:

test  4
3aca9ca3fa80158b765ece7d0a45f2e8  32
592cb30e95efc720c5accf425ed5f2fe46aa332d9980e6daa234797de49cda731c2c18e667b4dd71ba33797a3dcddd312ff9b03d802bf1cc09aacb2a176cf741  128
succeed

参考链接


snapd进程持续写盘导致系统卡顿

系统使用一段时间后,莫名其妙出现卡顿现象。发现硬盘灯闪个不停。

经查,是snapd进程持续不停的往硬盘写入数据,同时也会占用CPU。

写入量不大,但是磁盘占用率出奇的高。

导致系统出现卡顿,这个问题从ubuntu18.04到20.04实测都有,更老的版本没有实测不妄加批评。

解决办法网上找了N圈也没有找到解决办法。所以,只好……

干掉它!

$ sudo apt purge snapd

卸载后系统丝般顺滑。。

因为我暂时snap用的不多,加上也没别的办法,毕竟已经影响到系统的正常使用了,只好选择卸载。

有人说不能卸载,可以试试下面的命令:

$ sudo apt autoremove --purge snapd

卸载之后,继续重装一下,貌似也不再出现问题。

参考链接


Error 'Character set '#255' is not a compiled ch aracter set and is not specified in the '/usr/local/mariadb10/share/mysql/charse ts/Index.xml' file' on query. Default database: 'wordpress'. Query: 'BEGIN'

以前通过 家里ADSL上网无固定外网IP的群晖NAS安全实现与公网MySQL服务器主从同步 配置之后,群晖自带的 MariaDB 10.3.21 可以非常流畅的与服务器上的 MySQL 7.x 版本进行主从同步。

前两天系统从 ubuntu 18.04 升级到 ubuntu 20.04 (MySQL 8.x)之后,发现已经无法进行主从同步。

报告如下错误:

$ mysql -u root -p -e "show slave status\G;"
Enter password:
*************************** 1. row ***************************
                Slave_IO_State:
                   Master_Host: 10.8.0.1
                   Master_User: repl1
                   Master_Port: 3306
                 Connect_Retry: 60
               Master_Log_File: mysql-bin.001242
           Read_Master_Log_Pos: 8350259
                Relay_Log_File: mysqld10-relay-bin.000510
                 Relay_Log_Pos: 424
         Relay_Master_Log_File: mysql-bin.001222
              Slave_IO_Running: No
             Slave_SQL_Running: No
               Replicate_Do_DB:
           Replicate_Ignore_DB:
            Replicate_Do_Table:
        Replicate_Ignore_Table:
       Replicate_Wild_Do_Table:
   Replicate_Wild_Ignore_Table:
                    Last_Errno: 22
                    Last_Error: Error 'Character set '#255' is not a compiled character set and is not specified in the '/usr/local/mariadb10/share/mysql/charsets/Index.xml' file' on query. Default database: 'wordpress'. Query: 'BEGIN'
                  Skip_Counter: 0
           Exec_Master_Log_Pos: 125
               Relay_Log_Space: 137841279
               Until_Condition: None
                Until_Log_File:
                 Until_Log_Pos: 0
            Master_SSL_Allowed: Yes
            Master_SSL_CA_File: /var/packages/MariaDB10/etc/ssl/ca.pem
            Master_SSL_CA_Path: /var/packages/MariaDB10/etc/ssl
               Master_SSL_Cert: /var/packages/MariaDB10/etc/ssl/client-cert.pem
             Master_SSL_Cipher:
                Master_SSL_Key: /var/packages/MariaDB10/etc/ssl/client-key.pem
         Seconds_Behind_Master: NULL
 Master_SSL_Verify_Server_Cert: No
                 Last_IO_Errno: 0
                 Last_IO_Error:
                Last_SQL_Errno: 22
                Last_SQL_Error: Error 'Character set '#255' is not a compiled character set and is not specified in the '/usr/local/mariadb10/share/mysql/charsets/Index.xml' file' on query. Default database: 'wordpress'. Query: 'BEGIN'
   Replicate_Ignore_Server_Ids:
              Master_Server_Id: 1
                Master_SSL_Crl: /var/packages/MariaDB10/etc/ssl/ca.pem
            Master_SSL_Crlpath: /var/packages/MariaDB10/etc/ssl
                    Using_Gtid: No
                   Gtid_IO_Pos:
       Replicate_Do_Domain_Ids:
   Replicate_Ignore_Domain_Ids:
                 Parallel_Mode: conservative
                     SQL_Delay: 0
           SQL_Remaining_Delay: NULL
       Slave_SQL_Running_State:
              Slave_DDL_Groups: 0
Slave_Non_Transactional_Groups: 0
    Slave_Transactional_Groups: 0

网上查询很久,找到原因 MySQL 8.016 master with MariaDB 10.2 slave on AWS RDS, Character set '#255' is not a compiled character set

This could be a serious problem when replicating between MySQL 8.0 and MariaDB 10.x.

The default (for good technical reasons) `COLLATION` for the 8.0 is `utf8mb4_0900_ai_ci`. Note the "255" associated with it. MariaDB has not yet adopted the Unicode 9.0 collations.

Furthermore, Oracle (MySQL 8.0) did a major rewrite of the collation code, thereby possibly making collation tables incompatible.

probable fix is to switch to the next best general-purpose collation, `utf8mb4_unicode_520_ci` (246) (based on Unicode 5.20). This would require `ALTERing` all the columns' collations. `ALTER TABLE .. CONVERT TO ..` might be the fastest way. Those could be generated via a `SELECT .. information_schema.tables ...`.

大致原因就是MySql 8.0默认使用了最新的utf8mb4_0900_ai_ci字符集,然而MariaDB 10.3.x版本不支持这个字符集,导致无法同步。

8.0.17:

mysql> show collation like 'utf8mb4%';
+----------------------------+---------+-----+---------+----------+---------+---------------+
| Collation                  | Charset | Id  | Default | Compiled | Sortlen | Pad_attribute |
+----------------------------+---------+-----+---------+----------+---------+---------------+
| utf8mb4_0900_ai_ci         | utf8mb4 | 255 | Yes     | Yes      |       0 | NO PAD        |
| utf8mb4_0900_as_ci         | utf8mb4 | 305 |         | Yes      |       0 | NO PAD        |
| utf8mb4_0900_as_cs         | utf8mb4 | 278 |         | Yes      |       0 | NO PAD        |
| utf8mb4_0900_bin           | utf8mb4 | 309 |         | Yes      |       1 | NO PAD        |
| utf8mb4_bin                | utf8mb4 |  46 |         | Yes      |       1 | PAD SPACE     |
| utf8mb4_croatian_ci        | utf8mb4 | 245 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_cs_0900_ai_ci      | utf8mb4 | 266 |         | Yes      |       0 | NO PAD        |
| utf8mb4_cs_0900_as_cs      | utf8mb4 | 289 |         | Yes      |       0 | NO PAD        |
| utf8mb4_czech_ci           | utf8mb4 | 234 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_danish_ci          | utf8mb4 | 235 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_da_0900_ai_ci      | utf8mb4 | 267 |         | Yes      |       0 | NO PAD        |
| utf8mb4_da_0900_as_cs      | utf8mb4 | 290 |         | Yes      |       0 | NO PAD        |
| utf8mb4_de_pb_0900_ai_ci   | utf8mb4 | 256 |         | Yes      |       0 | NO PAD        |
| utf8mb4_de_pb_0900_as_cs   | utf8mb4 | 279 |         | Yes      |       0 | NO PAD        |
| utf8mb4_eo_0900_ai_ci      | utf8mb4 | 273 |         | Yes      |       0 | NO PAD        |
| utf8mb4_eo_0900_as_cs      | utf8mb4 | 296 |         | Yes      |       0 | NO PAD        |
| utf8mb4_esperanto_ci       | utf8mb4 | 241 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_estonian_ci        | utf8mb4 | 230 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_es_0900_ai_ci      | utf8mb4 | 263 |         | Yes      |       0 | NO PAD        |
| utf8mb4_es_0900_as_cs      | utf8mb4 | 286 |         | Yes      |       0 | NO PAD        |
| utf8mb4_es_trad_0900_ai_ci | utf8mb4 | 270 |         | Yes      |       0 | NO PAD        |
| utf8mb4_es_trad_0900_as_cs | utf8mb4 | 293 |         | Yes      |       0 | NO PAD        |
| utf8mb4_et_0900_ai_ci      | utf8mb4 | 262 |         | Yes      |       0 | NO PAD        |
| utf8mb4_et_0900_as_cs      | utf8mb4 | 285 |         | Yes      |       0 | NO PAD        |
| utf8mb4_general_ci         | utf8mb4 |  45 |         | Yes      |       1 | PAD SPACE     |
| utf8mb4_german2_ci         | utf8mb4 | 244 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_hr_0900_ai_ci      | utf8mb4 | 275 |         | Yes      |       0 | NO PAD        |
| utf8mb4_hr_0900_as_cs      | utf8mb4 | 298 |         | Yes      |       0 | NO PAD        |
| utf8mb4_hungarian_ci       | utf8mb4 | 242 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_hu_0900_ai_ci      | utf8mb4 | 274 |         | Yes      |       0 | NO PAD        |
| utf8mb4_hu_0900_as_cs      | utf8mb4 | 297 |         | Yes      |       0 | NO PAD        |
| utf8mb4_icelandic_ci       | utf8mb4 | 225 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_is_0900_ai_ci      | utf8mb4 | 257 |         | Yes      |       0 | NO PAD        |
| utf8mb4_is_0900_as_cs      | utf8mb4 | 280 |         | Yes      |       0 | NO PAD        |
| utf8mb4_ja_0900_as_cs      | utf8mb4 | 303 |         | Yes      |       0 | NO PAD        |
| utf8mb4_ja_0900_as_cs_ks   | utf8mb4 | 304 |         | Yes      |      24 | NO PAD        |
| utf8mb4_latvian_ci         | utf8mb4 | 226 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_la_0900_ai_ci      | utf8mb4 | 271 |         | Yes      |       0 | NO PAD        |
| utf8mb4_la_0900_as_cs      | utf8mb4 | 294 |         | Yes      |       0 | NO PAD        |
| utf8mb4_lithuanian_ci      | utf8mb4 | 236 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_lt_0900_ai_ci      | utf8mb4 | 268 |         | Yes      |       0 | NO PAD        |
| utf8mb4_lt_0900_as_cs      | utf8mb4 | 291 |         | Yes      |       0 | NO PAD        |
| utf8mb4_lv_0900_ai_ci      | utf8mb4 | 258 |         | Yes      |       0 | NO PAD        |
| utf8mb4_lv_0900_as_cs      | utf8mb4 | 281 |         | Yes      |       0 | NO PAD        |
| utf8mb4_persian_ci         | utf8mb4 | 240 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_pl_0900_ai_ci      | utf8mb4 | 261 |         | Yes      |       0 | NO PAD        |
| utf8mb4_pl_0900_as_cs      | utf8mb4 | 284 |         | Yes      |       0 | NO PAD        |
| utf8mb4_polish_ci          | utf8mb4 | 229 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_romanian_ci        | utf8mb4 | 227 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_roman_ci           | utf8mb4 | 239 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_ro_0900_ai_ci      | utf8mb4 | 259 |         | Yes      |       0 | NO PAD        |
| utf8mb4_ro_0900_as_cs      | utf8mb4 | 282 |         | Yes      |       0 | NO PAD        |
| utf8mb4_ru_0900_ai_ci      | utf8mb4 | 306 |         | Yes      |       0 | NO PAD        |
| utf8mb4_ru_0900_as_cs      | utf8mb4 | 307 |         | Yes      |       0 | NO PAD        |
| utf8mb4_sinhala_ci         | utf8mb4 | 243 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_sk_0900_ai_ci      | utf8mb4 | 269 |         | Yes      |       0 | NO PAD        |
| utf8mb4_sk_0900_as_cs      | utf8mb4 | 292 |         | Yes      |       0 | NO PAD        |
| utf8mb4_slovak_ci          | utf8mb4 | 237 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_slovenian_ci       | utf8mb4 | 228 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_sl_0900_ai_ci      | utf8mb4 | 260 |         | Yes      |       0 | NO PAD        |
| utf8mb4_sl_0900_as_cs      | utf8mb4 | 283 |         | Yes      |       0 | NO PAD        |
| utf8mb4_spanish2_ci        | utf8mb4 | 238 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_spanish_ci         | utf8mb4 | 231 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_sv_0900_ai_ci      | utf8mb4 | 264 |         | Yes      |       0 | NO PAD        |
| utf8mb4_sv_0900_as_cs      | utf8mb4 | 287 |         | Yes      |       0 | NO PAD        |
| utf8mb4_swedish_ci         | utf8mb4 | 232 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_tr_0900_ai_ci      | utf8mb4 | 265 |         | Yes      |       0 | NO PAD        |
| utf8mb4_tr_0900_as_cs      | utf8mb4 | 288 |         | Yes      |       0 | NO PAD        |
| utf8mb4_turkish_ci         | utf8mb4 | 233 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_unicode_520_ci     | utf8mb4 | 246 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_unicode_ci         | utf8mb4 | 224 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_vietnamese_ci      | utf8mb4 | 247 |         | Yes      |       8 | PAD SPACE     |
| utf8mb4_vi_0900_ai_ci      | utf8mb4 | 277 |         | Yes      |       0 | NO PAD        |
| utf8mb4_vi_0900_as_cs      | utf8mb4 | 300 |         | Yes      |       0 | NO PAD        |
| utf8mb4_zh_0900_as_cs      | utf8mb4 | 308 |         | Yes      |       0 | NO PAD        |
+----------------------------+---------+-----+---------+----------+---------+---------------+
75 rows in set (0.01 sec)

MariaDB 10.2.30:

mysql> SHOW COLLATION LIKE 'utf8mb4%';
+------------------------------+---------+------+---------+----------+---------+
| Collation                    | Charset | Id   | Default | Compiled | Sortlen |
+------------------------------+---------+------+---------+----------+---------+
| utf8mb4_general_ci           | utf8mb4 |   45 | Yes     | Yes      |       1 |
| utf8mb4_bin                  | utf8mb4 |   46 |         | Yes      |       1 |
| utf8mb4_unicode_ci           | utf8mb4 |  224 |         | Yes      |       8 |
| utf8mb4_icelandic_ci         | utf8mb4 |  225 |         | Yes      |       8 |
| utf8mb4_latvian_ci           | utf8mb4 |  226 |         | Yes      |       8 |
| utf8mb4_romanian_ci          | utf8mb4 |  227 |         | Yes      |       8 |
| utf8mb4_slovenian_ci         | utf8mb4 |  228 |         | Yes      |       8 |
| utf8mb4_polish_ci            | utf8mb4 |  229 |         | Yes      |       8 |
| utf8mb4_estonian_ci          | utf8mb4 |  230 |         | Yes      |       8 |
| utf8mb4_spanish_ci           | utf8mb4 |  231 |         | Yes      |       8 |
| utf8mb4_swedish_ci           | utf8mb4 |  232 |         | Yes      |       8 |
| utf8mb4_turkish_ci           | utf8mb4 |  233 |         | Yes      |       8 |
| utf8mb4_czech_ci             | utf8mb4 |  234 |         | Yes      |       8 |
| utf8mb4_danish_ci            | utf8mb4 |  235 |         | Yes      |       8 |
| utf8mb4_lithuanian_ci        | utf8mb4 |  236 |         | Yes      |       8 |
| utf8mb4_slovak_ci            | utf8mb4 |  237 |         | Yes      |       8 |
| utf8mb4_spanish2_ci          | utf8mb4 |  238 |         | Yes      |       8 |
| utf8mb4_roman_ci             | utf8mb4 |  239 |         | Yes      |       8 |
| utf8mb4_persian_ci           | utf8mb4 |  240 |         | Yes      |       8 |
| utf8mb4_esperanto_ci         | utf8mb4 |  241 |         | Yes      |       8 |
| utf8mb4_hungarian_ci         | utf8mb4 |  242 |         | Yes      |       8 |
| utf8mb4_sinhala_ci           | utf8mb4 |  243 |         | Yes      |       8 |
| utf8mb4_german2_ci           | utf8mb4 |  244 |         | Yes      |       8 |
| utf8mb4_croatian_mysql561_ci | utf8mb4 |  245 |         | Yes      |       8 |
| utf8mb4_unicode_520_ci       | utf8mb4 |  246 |         | Yes      |       8 | <--
| utf8mb4_vietnamese_ci        | utf8mb4 |  247 |         | Yes      |       8 |
| utf8mb4_croatian_ci          | utf8mb4 |  608 |         | Yes      |       8 |
| utf8mb4_myanmar_ci           | utf8mb4 |  609 |         | Yes      |       8 |
| utf8mb4_thai_520_w2          | utf8mb4 |  610 |         | Yes      |       4 |
| utf8mb4_general_nopad_ci     | utf8mb4 | 1069 |         | Yes      |       1 |
| utf8mb4_nopad_bin            | utf8mb4 | 1070 |         | Yes      |       1 |
| utf8mb4_unicode_nopad_ci     | utf8mb4 | 1248 |         | Yes      |       8 |
| utf8mb4_unicode_520_nopad_ci | utf8mb4 | 1270 |         | Yes      |       8 |
+------------------------------+---------+------+---------+----------+---------+
33 rows in set (0.00 sec)

目前暂时没打算修改主服务器上的数据库,暂时等 MariaDB 更新吧。

参考链接


笛卡尔乘积

笛卡尔乘积是指在数学中,两个集合XY的笛卡尔积(Cartesian product),又称直积,表示为X×Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员。

假设集合A={a, b},集合B={0, 1, 2},则两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。

矩阵的笛卡尔乘积就是矩阵的直积

讲到笛卡尔乘积,就难免会提到数据库中的 "自然连接(⋈)"。

要求:连接两个表 R ⋈ S
(表中标黄的数据后面会说到)

先看两个表头,发现A C是重复出现的

所以连接后,新表头为

然后把A C这两列单独拎出来,如下

发现有两组相同的,(也就是上面标红的数据
所以这两个表连接之后只有 两条 记录。

此时,可以做出下图

这时候从原来的R表、S表中找到对应数据填上

这样表格就完成了。

然后根据上述过程,尝试完成下面的例题:

结果:

我们还可以知道 当R和S没有公共属性时,则R⋈S = RXS
(没有公共属性,即每个属性都有它们唯一确定的值。所以RXS就没有需要划去的,也没有相同属性需要整合)

参考链接


iptables之recent模块小结(ubuntu 16.04/20.04)

iptables的recent模块用于限制一段时间内的连接数, 是谨防大量请求攻击的必杀绝技! 善加利用该模块可充分保证服务器安全。

recent常用参数

  • --name      设定列表名称,即设置跟踪数据库的文件名. 默认DEFAULT;
  • --rsource   源地址,此为默认。 只进行数据库中信息的匹配,并不会对已存在的数据做任何变更操作;
  • --rdest       目的地址;
  • --seconds  指定时间内. 当事件发生时,只会匹配数据库中前"几秒"内的记录,--seconds必须与--rcheck或--update参数共用;
  • --hitcount   命中次数. hits匹配重复发生次数,必须与--rcheck或--update参数共用;
  • --set           将地址添加进列表,并更新信息,包含地址加入的时间戳。 即将符合条件的来源数据添加到数据库中,但如果来源端数据已经存在,则更新数据库中的记录信息;
  • --rcheck     检查地址是否在列表,以第一个匹配开始计算时间;
  • --update    和rcheck类似,以最后一个匹配计算时间。 如果来源端的数据已存在,则将其更新;若不存在,则不做任何处理;
  • --remove   在列表里删除相应地址,后跟列表名称及地址。如果来源端数据已存在,则将其删除,若不存在,则不做任何处理;

recent模块需要注意的地方

  1. 目录/proc/net/下的xt_recent目录是在启用recent模块之后才有的,如果没有在iptables中使用recent模块,/proc/net/目录中是没有xt_recent目录的;
  2. 因recent模块最多只能记录20条记录,所以当源发送的数据包超过20后,recent模块的计数器会立刻减掉20,这也就是为什么old_packets的值就总是处于1-20之间;
  3. 如果配合seconds参数使用的是--rcheck参数而不是--update,则recent模块会从收到第一个数据包开始计算阻断时间,而--update是从收到的最后一个数据包开始计算阻断时间,即如果服务器在8点收到了源发出第一个icmp数据包,在8点15分收到源发出的第20个数据包,如果使用的是--rcheck参数,那么8点半的时候,用户就又可以发送icmp数据包了,如果使用是--update参数,则用户必须等到8点40才能发送icmp数据包;
  4. 当源发送数据包的个数大于或等于recent模块的hitcount参数所指定的值时,相应的iptables规则才会被激活;

recent命令大体有如下三个排列组合

  • --set句在前,--update(或--rcheck)句在后;
  • --update(或--rcheck)句在前,--set句在后;
  • --set句带或不带-j ACCEPT。

基本是上面这三项的排列组合;

下面通过几个案例进行说明

1)  利用iptables的recent模块来抵御简单的DOS攻击, 下面以限制ssh远程连接为例

$ iptables -I INPUT -p tcp --dport 22 -m connlimit --connlimit-above 3 -j DROP

$ iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH

$ iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds  300 --hitcount 3 --name SSH -j DROP

以上三条规则的解释如下:

  1. 利用connlimit模块将单IP的并发设置为3;会误杀使用NAT上网的用户,可以根据实际情况增大该值;
  2. 利用recent和state模块限制单IP在300s内只能与本机建立3个新连接。被限制一分钟后即可恢复访问。
  3. 第一句是记录访问tcp 22端口的新连接,记录名称为SSH, --set 记录数据包的来源IP,如果IP已经存在将更新已经存在的条目
  4. 第三句是指SSH记录中的IP,300s内发起超过3次连接则拒绝此IP的连接。      --update 是指每次建立连接都更新列表;      --seconds必须与--update同时使用      --hitcount必须与--update同时使用
  5. 可以使用下面的这句记录日志:
$ iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --name SSH --second 300 --hitcount 3 -j LOG --log-prefix "SSH Attack"

recent模块可以看作iptables里面维护了一个地址列表,这个地址列表可以通过"--set"、"--update"、"--rcheck"、"--remove"四种方法来修改列表,每次使用时只能选用一种。 还可附带"--name"参数来指 定列表的名字(默认为DEFAULT),"--rsource"、"--rdest"指示当前方法应用到数据包的源地址还是目的地址(默认是前者)。recent语句都带有布尔型返回值,每次执行若结果为真,则会执行后续的语句,比如"-j ACCEPT"之类的。"--seconds"参数表示限制包地址被记录进列表的时间要小于等于后面的时间。

基于上面的说明,现在来看四个基本方法的作用:

  • --set       将地址添加进列表,并更新信息,包含地址加入的时间戳。
  • --rcheck  检查地址是否在列表。
  • --update  跟rcheck一样,但会刷新时间戳。
  • --remove 就是在列表里删除地址,如果要删除的地址不存在就会返回假。

例1:限制无法ssh直接连接服务器,需先用较大包ping一下,此时在15秒内才可以连接上

$ iptables -P INPUT DROP

$ iptables -A INPUT -s 127.0.0.1/32 -j ACCEPT

$ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

$ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 128 -m recent --set --name SSHOPEN --rsource -j ACCEPT

$ iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT

$ iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --rcheck --seconds 15 --name SSHOPEN --rsource -j ACCEPT

以上命令解说

  1. 将INPUT链默认策略置为DROP,当包走完INPUT链而没被拿走时就会丢弃掉;
  2. 本地localhost的包全部接受;
  3. 对于已建立连接或是与已连接相关的包都接受,服务器对外连接回来的包一般都走这条;基本环境已经配好了,现在开始要为连接到服务器的ssh打开通路。
  4. icmp类型 8 是ping包;指定包大小为 128 字节;recent用的列表名称为SSHOPEN,列表记录源地址。符合上述条件的数据包都接收。如果ping包内容为 100 字节,则加上IP头, ICMP头的 28 字节,总共 128 字节。
  5. 接受一般的ping包;
  6. 对连接ssh 22端口的连接进行处理,来源于SSHOPEN源地址列表并且在列表时间小于等于15秒的才放行。

例2:  限制每ip在一分钟内最多对服务器只能有8个http连接

$ iptables -I INPUT -p tcp --dport 80 -d 192.168.10.10 -m state --state NEW -m recent --name httpuser --set

$ iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j LOG --log-prefix 'HTTP attack: '

$ iptables -A INPUT -m recent --update --name httpuser --seconds 60 --hitcount 9 -j DROP

以上命令解说

  1. 192.168.10.10是服务器ip
  2. 参数-I,将本规则插入到 INPUT 链里头的最上头。只要是 TCP连接,目标端口是80,目标 IP是我们服务器的IP,刚刚新被建立起来时,我们就将这个联机列入 httpuser 这分  清单中;
  3. 参数-A,将本规则附在 INPUT 链的最尾端。只要是60秒内,同一个来源连续产生多个联机,到达第9个联机时,我们对此联机留下Log记录。记录行会以 HTTP attack 开头。  每一次的本规则比对, –update 均会更新httpuser清单中的列表;
  4. 参数-A,将本规则附在 INPUT 链的最尾端。同样的比对条件,但是本次的动作则是将此连接丢掉;
  5. 所以,这三行规则表示,我们允许一个客户端,每一分钟内可以接上服务器8个。具体数值可以看运维者决定。这些规则另外也可以用在其它对外开放的网络服务上,例如port  22 (SSH), port 25 (smtp email)。

2) 对连接到服务器B的SSH连接进行限制,每个IP每小时只限连接5次

-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --rcheck --seconds 3600 --hitcount 5 -j DROP
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --set -j ACCEPT

服务器A连接服务器B的SSH服务的数据包流程,假设以下数据包是在一小时(3600秒)内到达服务器B(iptables配置如上)的:

  1. 当这个服务器A的第1个SSH包到达服务器B,规则1检查SSHPOOL列表中这个源IP是否有hitcount,因为是第一个包,显而易见,列表是0,规则1判定这个数据包不必执行      DROP,并且也不处理这个数据包,将数据包转给下条规则。
  2. 规则2将这个数据包计入SSHPOOL列表,就是做+1,因为规则中有-j ACCEPT,规则2放行这个包。
  3. 第1个数据包进入服务器B,不用再在iptables里转了。
  4. 当第2个SSH包到达服务器B,规则1检查SSHPOOL列表的hitcount,发现是1没有超过5,于是判定不执行DROP并转给下条规则处理。
  5. 规则2在SSHPOOL中+1,并放行,第2个数据包进入服务器B。
  6. 第3、4、5个包同上。 g) 第6个包到达服务器B,规则1检查SSHPOOL列表中的hitcount,发现是5了已经连接5次了,于是规则2执行DROP,不必再转给下条规则了丢弃该包。 h) 第7、8…个包同上。

实际上recent的处理更为复杂, 从上面的流程可以看出,--set的功能在于计录数据包,将源IP加入列表。--rcheck(update)的功能在于判定数据包在seconds和hitcount条件下是否要DROP。

如果采用下面的配置, 则必须在INPUT链的默认策略为ACCEPT的情况下才能生效并使用, 否则(即INPUT链的默认策略为DROP)所有的SSH包都被丢弃了!!!!

-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT                            //必须添加这个前提条件才能生效!
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --set
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --rcheck --seconds 3600 --hitcount 5 -j DROP

这个命令与上面命令的区别在于:set句在前,且set句不带-j ACCEPT。这导致数据包的流程也不同。

  1. 第1个数据包到达规则1后马上计入SSHPOOL列表,并且规则1因为没有-j ACCEPT,直接将数据包转给下条规则。规则2拿到这个数据包后检查SSHPOOL列表,发现是1,也  不处理这个包,转给下条规则。如果后续的规则都没有再处理这个数据包,则最后由INPUT链的默认策略ACCEPT处理。由于上面的策略是DROP,所以丢弃该包,于是这两行命令在我的服务器上不能用。
  2. 这里有个问题,由于set句在前,数据包进入是先计入列表,再判定是否合法。这导致第5个包到达后,先在列表中+1,结果是5,再由规则2判定,发现是5,结果丢弃该包,    最后真正ACCEPT的只有4个包。其实个人认为这样写的代码不符合正常的思维逻辑, 而且这样写只能正常工作于默认策略是ACCEPT的情况,所以不建议用这个版本的命令,我的版本ACCEPT、DROP策略都能用。

从上面可以看出,在使用recent模块的命令的时候,一定要先确认iptables的INPUT链的默认策略是什么。

接着说下--rcheck 和 --update的区别 --rcheck从第1个包开始计算时间,--update是在rcheck的基础上增加了从最近的DROP包开始计算阻断时间,具有准许时间和阻断时间,update会更新last-seen时间戳。

就拿上面那个配置案例来说, rcheck是接收到第1个数据包时开始计时,一个小时内仅限5次连接,后续的包丢弃,直到一小时过后又可以继续连接。update则是接收到第1个数据包时计算准许时间,在一个小时的准许时间内仅限5次连接,当有包被丢弃时,从最近的丢弃包开始计算阻断时间,在一个小时的阻断时间内没有接收到包,才可以继续连接。所以rcheck类似令牌桶,一小时给你5次,用完了抱歉等下个小时吧。update类似网银,连续输错5次密码,停止一小时,只不过update更严格,阻断时间是从最近的一次输错时间开始算,比如输错了5次,过了半个小时又输错一次,这时阻断时间不是剩半小时,而是从第6次重新计算,剩一小时. 

可以拿下面这个命令进行测试, 自行替换rcheck、update,然后ping一下就明白了:

-A INPUT -p icmp -m recent --name PINGPOOL --rcheck --seconds 30 --hitcount 5 -j DROP
-A INPUT -p icmp -m recent --name PINGPOOL --set -j ACCEPT
 
-A INPUT -p icmp -m recent --name PINGPOOL --update --seconds 30 --hitcount 5 -j DROP
-A INPUT -p icmp -m recent --name PINGPOOL --set -j ACCEPT

温馨提示: ICMP包和UDP包在iptables中的state情况是一样的,因为是无状态的,不同于TCP,iptables可以靠SYN等flags确定state,而iptables是基于ICMP包/UDP包到达服务器的间隔时间来确定state的。比如在做上面测试的时候,使用ping 192.168.10.10 -t时,除了第一个ICMP包state是NEW,后续的包state都是ESTABLISHED,结果因为前面有一句:

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

结果测试ping一直是通的,这个一定要弄明白!

3) 限制80端口60秒内每个IP只能发起10个新连接,超过记录日记及丢失数据包,可防CC及非伪造IP的syn flood

$ iptables -A INPUT -p tcp --dport 80 --syn -m recent --name webpool --rcheck --seconds 60 --hitcount 10 -j LOG --log-prefix 'DDOS:' --log-ip-options

$ iptables -A INPUT -p tcp --dport 80 --syn -m recent --name webpool --rcheck --seconds 60 --hitcount 10 -j DROP

$ iptables -A INPUT -p tcp --dport 80 --syn -m recent --name webpool --set -j ACCEPT

以上命令解说

  • 第1行规则表示: 60秒10个新连接,超过记录日志。
  • 第2行规则表示: 60秒10个新连接,超过记录日志。
  • 第3行规则表示: 范围内允许通过。
    即每个IP目标端口为80的新连接会记录在案,可在/proc/net/xt_recent/目录内查看,rcheck检查此IP是否在案及请求次数,如果超过规则就丢弃数据包,否则进入下条规则并更新列表信息。

发送特定指定执行相应操作,按上面设置, 如果自己IP被阻止了,可设置解锁。

$ iptables -A INPUT -p tcp --dport 5000 --syn -j LOG --log-prefix "WEBOPEN: "
#记录日志,前缀WEBOPEN:

$ iptables -A INPUT -p tcp --dport 5000 --syn -m recent --remove --name webpool --rsource -j REJECT --reject-with tcp-reset
#符合规则即删除webpool列表内的本IP记录

4)  默认封闭SSH端口,为您的SSH服务器设置开门暗语

$ iptables -A INPUT -p tcp --dport 50001 --syn -j LOG --log-prefix "SSHOPEN: "
#记录日志,前缀SSHOPEN:

$ iptables -A INPUT -p tcp --dport 50001 --syn -m recent --name sshopen --set --rsource -j REJECT --reject-with tcp-reset
#目标端口tcp 50001的新数据设定列表为sshopen返回TCP重置,并记录源地址。

$ iptables -A INPUT -p tcp --dport 22 --syn -m recent --name sshopen --rcheck --seconds 15 --rsource -j ACCEPT
#开启SSH端口,15秒内允许记录的源地址登录SSH。
 
#开门钥匙
nc host 50001 
telnet host 50001
nmap -sS host 50001

5) 指定端口容易被破解密钥,可以使用ping指定数据包大小为开门钥匙

$ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -j LOG --log-prefix "SSHOPEN: "
#记录日志,前缀SSHOPEN:

$ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -m recent --name sshopen --set --rsource -j ACCEPT

#指定数据包78字节,包含IP头部20字节,ICMP头部8字节。
$ iptables -A INPUT -p tcp --dport 22 --syn -m recent --name sshopen --rcheck --seconds 15 --rsource -j ACCEPT

按照上面配置后, 依然无法ssh登录到指定主机, 因为没有添加"INPUT链的默认策略为ACCEPT"的前提, 即需要下面这个前提条件!

$ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

整理后的配置规则

$ iptables -F

$ iptables -X

$ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

$ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -j LOG --log-prefix 'SSH_OPEN_KEY'

$ iptables -A INPUT -p icmp --icmp-type 8 -m length --length 78 -m recent --name openssh --set --rsource -j ACCEPT

$ iptables -A INPUT -p tcp --dport 22 --syn -m recent --name openssh --rcheck --seconds 60 --rsource -j ACCEPT

$ iptables -P INPUT DROP

针对上面整理后的配置规则说明

  • 第1,2行规则表示: 清空原有的iptables规则
  • 第3行规则表示: 已经建立成功的连接和与主机发送出去的包相关的数据包都接受,如果没有这一步,后面的tcp连接无法建立起来
  • 第4行规则表示: 出现长度为78字节icmp回响包在/var/log/syslog生成log,log以SSH_OPEN_KEY开头
  • 第5行规则表示: 出现长度为78字节icmp回响包,将源地址信息记录在openssh文件中,并接受
  • 第6行规则表示: 对于openssh文件中的源地址60s以内发送的ssh连接SYN请求予以接受
  • 第7行规则表示: 将INPUT链的默认策略设置为drop

调试过程:

  1. 如果没有设置第3行规则,则无法建立ssh连接
  2. 在没有第3行规则的情况下,设置第6行如果不加--syn,则可以ssh连接一会儿,过一会儿又自动断线,除非ping一下目的地址. 原理是:ping目的地址,则会更新openssh的时间,这样ssh连接还在60s之内,所以可以通信,过一会儿,60s超时,则就会断开ssh连接。如果加了--syn,只能进行开始的syn,无法正常连接

在客户机上,如果需要ssh到主机,需要先ping主机进行解锁

$ ping -s 50 ip    #linux主机的ip
$ ping -l 50 ip     #windows主机的ip

并在一分钟之内ssh到主机,这样在/proc/net/xt_recent/目录下生成openssh文件, 内容如下:

src=192.168.10.10
ttl: 53
last_seen: 42963x6778
oldest_pkt: 1 4296376778, 4295806644, 4295806895, 4295807146, 4295826619, 4295826870, 4295827122, 4295827372, 4295833120, 4295833369, 4295834525, 4295834777, 4295872016, 4295872267, 4295872519, 4295872769, 4295889154, 4295889406, 4295889658, 4295889910

参考链接


Iptables之recent模块小结

window.onerror的总结

最近一直在做前端js错误监控的工作,在不断的打磨和完善中,发现里面还是知识点不少,现在就前端js错误监控做一些笔记和总结

我们知道前端js错误监控主要是利用了window.onerror函数来实现,onerror函数会在页面发生js错误时被调用。

window.onerror = function(message, source, lineno, colno, error) { ... }

我们可以看到函数正常是可以收集到错误字符串信息、发生错误的js文件,错误所在的行数、列数、和Error对象(里面会有调用堆栈信息等)。

我们只需要把这些信息回传到server端即可,再配合sourcemap的话我们就可以知道是源码中的哪一行出错了,从而实现完美的错误实时监控系统了。然而要完美还是需要做很多工作的。

继续阅读window.onerror的总结

ubuntu 20.04配置rsync服务通过ssh同步

  • 安装配置为数据同步进行准备
$ sudo apt-get install rsync

# ubuntu 16.04/18.04默认源中都存在rssh ,但是 ubuntu 20.04开始,已经没办法从系统源中安装了。
# 原因在于 rssh 存在安全漏洞,并且长时间没有修复,并且已经没人维护了,我们使用rrsync替代 

$ export RSH_RSYNC_USER=rsh_backup

$ export RSH_RSYNC_USER_PASSWORD=rsh_password

# 如果需要备份/var/log 目录,建议把用户组设置成 adm 用户组,否则很多日志没办法读取
# sudo useradd -s "" $RSH_RSYNC_USER -g users -G adm
# 后期也可以通过 sudo usermod -g users -G adm $RSH_RSYNC_USER 更改用户组

# 不配置任何登陆默认shell,以便于 rrsync 接管
$ sudo useradd -s "" $RSH_RSYNC_USER

$ echo $RSH_RSYNC_USER:$RSH_RSYNC_USER_PASSWORD | sudo chpasswd

# 创建配置文件目录,增加配置信息
$ sudo mkdir /home/$RSH_RSYNC_USER

$ sudo chown -R $RSH_RSYNC_USER /home/$RSH_RSYNC_USER

$ sudo mkdir /home/$RSH_RSYNC_USER/.ssh

$ sudo chown -R $RSH_RSYNC_USER /home/$RSH_RSYNC_USER/.ssh

$ sudo touch /home/$RSH_RSYNC_USER/.ssh/authorized_keys

$ sudo chown -R $RSH_RSYNC_USER /home/$RSH_RSYNC_USER/.ssh/authorized_keys

# 配置许可访问的目录
$ echo 'command="/usr/bin/rrsync -ro /var/www /data/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding' | sudo tee /home/$RSH_RSYNC_USER/.ssh/authorized_keys
  • 客户端同步,测试是否正常工作
$ export RSH_RSYNC_USER=rsh_backup 

$ export RSH_RSYNC_USER_PASSWORD=rsh_password

$ export RSH_RSYNC_PORT=22

$ export REMOTE_SERVER=www.mobibrw.com

$ export SYNC_DIR=/var/www

# 测试是否可以正常工作,另一个方面是许可ssh的登录key否则我们后续没办法使用sshpass
# 可以使用 --exclude 参数忽略指定的目录,相对路径,不是绝对路径
$ sudo rsync -avzP --delete -e 'ssh -p $RSH_RSYNC_PORT' $RSH_RSYNC_USER@$REMOTE_SERVER:$SYNC_DIR $SYNC_DIR
  • 转化为定时任务,自动同步
# 安装密码自动填写软件
$ sudo apt-get install sshpass

$ cd ~

$ touch backup_rsync.sh

$ chmod +x backup_rsync.sh

$ echo -e '#!'"/bin/bash\n" >> backup_rsync.sh

$ sed -i '$a\export RSH_RSYNC_USER=rsh_backup' backup_rsync.sh

$ sed -i '$a\export RSH_RSYNC_USER_PASSWORD=rsh_password' backup_rsync.sh

$ sed -i '$a\export RSH_RSYNC_PORT=22' backup_rsync.sh

$ sed -i '$a\export REMOTE_SERVER=www.mobibrw.com' backup_rsync.sh

# 此处注意最后的分隔符/如果没有这个分隔符,会导致目标目录多一个名为www的父目录
$ sed -i '$a\export SYNC_DIR=/var/www/' backup_rsync.sh

# 可以使用 --exclude 参数忽略指定的目录,相对路径,不是绝对路径
$ sed -i '$a\sshpass -p $RSH_RSYNC_USER_PASSWORD rsync -avzP --delete -e \"ssh -p $RSH_RSYNC_PORT\" $RSH_RSYNC_USER@$REMOTE_SERVER:$SYNC_DIR $SYNC_DIR' backup_rsync.sh

# 打开计划任务的日志,默认不开
$ sudo sed -i -r "s/#cron\.\*[ \t]*\/var\/log\/cron.log/cron.*                         \/var\/log\/cron.log/g" /etc/rsyslog.d/50-default.conf

#write out current crontab
$ sudo crontab -l > addcron

#echo new cron into cron file ,每隔30分钟我们调度一次任务,前面是文件锁,防止并发冲突
#如果需要每小时执行一次,则修改为 "* */1 * * * flock ...."
#如果需要凌晨2点执行,则修改为 "* 2 * * * flock ...."
$ echo "30 * * * * flock -x -w 10 /dev/shm/backup_rsync_corn.lock -c \"bash `echo ~`/backup_rsync.sh\"" >> addcron

#install new cron file
$ sudo crontab addcron

$ rm addcron

$ sudo service cron restart

目前我的阿里云,腾讯云服务器之间的同步脚本如下:

#!/bin/bash

export RSH_RSYNC_USER=user
export RSH_RSYNC_USER_PASSWORD=password
export RSH_RSYNC_PORT=22
export REMOTE_SERVER=121.199.27.227

export SYNC_WWW_DIR=/var/www/

sshpass -p $RSH_RSYNC_USER_PASSWORD rsync -avzP --delete -e "ssh -p $RSH_RSYNC_PORT" $RSH_RSYNC_USER@$REMOTE_SERVER:$SYNC_WWW_DIR $SYNC_WWW_DIR --exclude "wordpress/wp-content/cache/" --exclude "wordpress/wp-content/plugins/crayon-syntax-highlighter/log.txt"

export SYNC_DATA_DIR=/data/
 
# 此处注意,如果使用 --exclude "/root/" 则忽略最顶部的root目录
# 如果使用 --exclude "root/" 则忽略所有路径中包含 root 的目录
# 主要 rsync 的 top-directroty 部分的规则
sshpass -p $RSH_RSYNC_USER_PASSWORD rsync -avzP --delete -e "ssh -p $RSH_RSYNC_PORT" $RSH_RSYNC_USER@$REMOTE_SERVER:$SYNC_DATA_DIR $RSYNC_DATA_DIR --exclude "/root/"

参考链接


Fix ufw service not loading after a reboot

I have a Ubuntu 18.04/20.04 server running ufw (Uncomplicated Firewall) and Docker. Docker relies on iptables-persistent, which is an interface to a much more powerful and complicated firewall that many people would rather avoid.

The problem here is that ufw and iptables-persistent are both ways for creating the same firewall. On my server, only one service would ever run at startup negating the other.

After a reboot ufw would always be disabled.

$ sudo ufw status

Status: inactive

Even though the ufw service is enabled, if you look closely, the active service has exited.

$ sudo systemctl status ufw 

● ufw.service - Uncomplicated firewall
    Loaded: loaded (/lib/systemd/system/ufw.service; enabled; vendor preset: enabled)
    Active: active (exited)

If I check the server services, both ufw and netfilter-persistent are enabled. netfilter-persistent is a means for managing iptables on Debian and Ubuntu systems.

$ sudo service --status-all

 [ + ]  netfilter-persistent
 [ + ]  ufw

The fix is simple; we need to tell the operating system to load ufw after the netfilter-persistent.

Find and backup the ufw service.

$ ls -l /lib/systemd/system/ufw.service

-rw-r--r-- 1 root root  266 Aug 15  2017  ufw.service
$ cd /lib/systemd/system/

$ sudo cp ufw.service ufw.service.original
$ cat /lib/systemd/system/ufw.service

 [Unit]
 Description=Uncomplicated firewall
 Documentation=man:ufw(8)
 DefaultDependencies=no
 Before=network.target

 [Service]
 Type=oneshot
 RemainAfterExit=yes
 ExecStart=/lib/ufw/ufw-init start quiet
 ExecStop=/lib/ufw/ufw-init stop

 [Install]
 WantedBy=multi-user.target

Update and save the modified service by appending `After=netfilter-persistent.service` to the `[Unit]` block.

$ sudo nano /lib/systemd/system/ufw.service
 [Unit]
 Description=Uncomplicated firewall
 Documentation=man:ufw(8)
 DefaultDependencies=no
 Before=network.target
 After=netfilter-persistent.service

 [Service]
 Type=oneshot
 RemainAfterExit=yes
 ExecStart=/lib/ufw/ufw-init start quiet
 ExecStop=/lib/ufw/ufw-init stop

 [Install]
 WantedBy=multi-user.target

Reboot and test.

$ sudo reboot
$ sudo ufw status

Status: active
 To                         Action      From
 --                         ------      ----
 OpenSSH                    ALLOW       Anywhere
 Nginx Full                 ALLOW       Anywhere

参考链接


How to Install and Configure Fail2ban on Ubuntu 20.04

Any service that is exposed to the Internet is at risk of malware attacks. For example, if you are running a service on a publicly available network, attackers can use brute-force attempts to sign in to your account.

Fail2ban is a tool that helps protect your Linux machine from brute-force and other automated attacks by monitoring the services logs for malicious activity. It uses regular expressions to scan log files. All entries matching the patterns are counted, and when their number reaches a certain predefined threshold, Fail2ban bans the offending IP using the system firewall for a specific length of time. When the ban period expires, the IP address is removed from the ban list.

This article describes how to install and configure Fail2ban on Ubuntu 20.04. 

低于 ubuntu 20.04 的系统,可以参考 ubuntu 16.04防止SSH暴力登录攻击 。

Installing Fail2ban on Ubuntu

The Fail2ban package is included in the default Ubuntu 20.04 repositories. To install it, enter the following command as root or user with sudo privileges :

$ sudo apt update

$ sudo apt install fail2ban

Once the installation is completed, the Fail2ban service will start automatically. You can verify it by checking the status of the service:

$ sudo systemctl status fail2ban

The output will look like this:

● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2020-08-19 06:16:29 UTC; 27s ago
       Docs: man:fail2ban(1)
   Main PID: 1251 (f2b/server)
      Tasks: 5 (limit: 1079)
     Memory: 13.8M
     CGroup: /system.slice/fail2ban.service
             └─1251 /usr/bin/python3 /usr/bin/fail2ban-server -xf start

That’s it. At this point, you have Fail2Ban running on your Ubuntu server.

Fail2ban Configuration

The default Fail2ban installation comes with two configuration files, `/etc/fail2ban/jail.conf` and `/etc/fail2ban/jail.d/defaults-debian.conf`. It is not recommended to modify these files as they may be overwritten when the package is updated.

Fail2ban reads the configuration files in the following order. Each `.local` file overrides the settings from the `.conf` file:

  • `/etc/fail2ban/jail.conf`
  • `/etc/fail2ban/jail.d/*.conf`
  • `/etc/fail2ban/jail.local`
  • `/etc/fail2ban/jail.d/*.local`

For most users, the easiest way to configure Fail2ban is to copy the `jail.conf` to `jail.local` and modify the `.local` file. More advanced users can build a `.local`configuration file from scratch. The `.local` file doesn’t have to include all settings from the corresponding `.conf` file, only those you want to override.

Create a `.local` configuration file from the default `jail.conf` file:

$ sudo cp /etc/fail2ban/jail.{conf,local}

To start configuring the Fail2ban server open, the `jail.local` file with your text editor :

$ sudo nano /etc/fail2ban/jail.local

The file includes comments describing what each configuration option does. In this example, we’ll change the basic settings.

Whitelist IP Addresses

IP addresses, IP ranges, or hosts that you want to exclude from banning can be added to the `ignoreip` directive. Here you should add your local PC IP address and all other machines that you want to whitelist.

Uncomment the line starting with `ignoreip` and add your IP addresses separated by space:

/etc/fail2ban/jail.local

ignoreip = 127.0.0.1/8 ::1 123.123.123.123 192.168.1.0/24

Ban Settings

The values of `bantime`, `findtime`, and `maxretry` options define the ban time and ban conditions.

`bantime` is the duration for which the IP is banned. When no suffix is specified, it defaults to seconds. By default, the `bantime` value is set to 10 minutes. Generally, most users will want to set a longer ban time. Change the value to your liking:

/etc/fail2ban/jail.local

bantime = 1d

To permanently ban the IP use a negative number.

`findtime` is the duration between the number of failures before a ban is set. For example, if Fail2ban is set to ban an IP after five failures (`maxretry`, see below), those failures must occur within the `findtime` duration.

/etc/fail2ban/jail.local

# 这个时间段内超过规定次数会被ban掉
findtime = 10m

`maxretry` is the number of failures before an IP is banned. The default value is set to five, which should be fine for most users.

/etc/fail2ban/jail.local

maxretry = 5

Email Notifications

Fail2ban can send email alerts when an IP has been banned. To receive emails, you need to have an SMTP installed on your server and change the default action, which only bans the IP to `%(action_mw)s`, as shown below:

/etc/fail2ban/jail.local

action = %(action_mw)s

`%(action_mw)s` bans the offending IP and sends an email with a whois report. If you want to include the relevant logs in the email, set the action to `%(action_mwl)s`.

You can also adjust the sending and receiving email addresses:

/etc/fail2ban/jail.local

destemail = admin@linuxize.com 
sender = root@linuxize.com

Fail2ban Jails

Fail2ban uses a concept of jails. A jail describes a service and includes filters and actions. Log entries matching the search pattern are counted, and when a predefined condition is met, the corresponding actions are executed.

Fail2ban ships with a number of jail for different services. You can also create your own jail configurations.

By default, only the ssh jail is enabled. To enable a jail, you need to add `enabled = true` after the jail title. The following example shows how to enable the proftpd jail:

/etc/fail2ban/jail.local

[proftpd]

port     = ftp,ftp-data,ftps,ftps-data
logpath  = %(proftpd_log)s
backend  = %(proftpd_backend)s

The settings we discussed in the previous section, can be set per jail. Here is an example:

/etc/fail2ban/jail.local

[sshd]
enabled   = true
maxretry  = 3
findtime  = 1d
bantime   = 4w
ignoreip  = 127.0.0.1/8 23.34.45.56

The filters are located in the `/etc/fail2ban/filter.d` directory, stored in a file with same name as the jail. If you have custom setup and experience with regular expressions you can fine tune the filters.

Each time you edit a configuration file, you need to restart the Fail2ban service for changes to take effect:

$ sudo systemctl restart fail2ban

Fail2ban Client

Fail2ban ships with a command-line tool named `fail2ban-client` that you can use to interact with the Fail2ban service.

To view all available options, invoke the command with the `-h` option:

$ fail2ban-client -h

This tool can be used to ban/unban IP addresses, change settings, restart the service, and more. Here are a few examples:

  • Check the jail status:

    $ sudo fail2ban-client status sshd
  • Unban an IP:

    $ sudo fail2ban-client set sshd unbanip 23.34.45.56
  • Ban an IP:

    $ sudo fail2ban-client set sshd banip 23.34.45.56

Conclusion

We’ve shown you how to install and configure Fail2ban on Ubuntu 20.04.

For more information on this topic, visit the Fail2ban documentation .

If you have questions, feel free to leave a comment below.

注意:如果服务器上启用了 UFW 防火墙,则几乎必然出现 Fail2ban 无法阻止攻击者 IP 的情况。

尽管 Fail2ban 已经提示阻止用户访问。但是由于 iptables 的顺序问题,根本不起作用,依旧在 /var/log/auth.log 中观察到不断的访问尝试。

解决方法如下:

$ sudo vim /etc/fail2ban/jail.local

banaction = iptables-multiport
banaction_allports = iptables-allports

更改为

banaction = ufw
banaction_allports = ufw

防火墙规则要求通过 UFW 进行设置。

重新载修改后的配置信息

$ sudo fail2ban-client reload

默认情况下,执行如下命令后:

$ sudo systemctl enable ufw

$ sudo ufw enable

UFW 服务器应该随着服务器重启自动启动,但是却经常没有启动,尤其是 ubuntu 20.04

参考 Fix ufw service not loading after a reboot 调整。

默认情况下,`Fail2ban`的配置是没办法阻止用户名尝试的,因此,我们需要新增如下配置才能解决问题。

First, define the filter for invalid users in `/etc/fail2ban/filter.d/sshd-invaliduser.conf`:

[INCLUDES]
before = common.conf

[Definition]
_daemon = sshd

failregex = ^%(__prefix_line)s[iI](?:llegal|nvalid) user .*? from <HOST>(?: port \d+)?\s*$
ignoreregex = 

[Init]
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

Then enable it in `/etc/fail2ban/jail.local`:

[sshd-invaliduser]
enabled = true
maxretry = 1
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s

默认情况下,`Fail2ban`的配置是没办法阻止root登陆尝试的,因此,我们需要新增如下配置才能解决问题。

[INCLUDES]
 
before = common.conf
 
[Definition]
 
_daemon = sshd
 
failregex = ^%(__prefix_line)spam_unix\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=\S*\s*rhost=<HOST>\S*\s*user=(root|admin)\s.*$
 
ignoreregex =
 
[Init]
 
maxlines = 10
 
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

Then enable it in `/etc/fail2ban/jail.local`:

[sshd-ban-root]
enabled = true
maxretry = 1
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
移除被禁止访问的IP

我们自己操作的时候,可能会把自己给禁止访问,此时需要手工从禁止列表中移除某些特定的IP

$ sudo fail2ban-client unban xx.xx.xx.xx

参考链接