还没想好用什么标题

0%

这是一个专题 记录学习python标准库的笔记及心得

简单http服务 SimpleHTTPServer
使用 python -m SimpleHTTPServer 默认启动8000端口

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
"""Simple HTTP Server.

This module builds on BaseHTTPServer by implementing the standard GET
and HEAD requests in a fairly straightforward manner.

"""


__version__ = "0.6"

__all__ = ["SimpleHTTPRequestHandler"]

import os
import posixpath
import BaseHTTPServer
import urllib
import urlparse
import cgi
import sys
import shutil
import mimetypes
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO


class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

"""Simple HTTP request handler with GET and HEAD commands.

This serves files from the current directory and any of its
subdirectories. The MIME type for files is determined by
calling the .guess_type() method.

The GET and HEAD requests are identical except that the HEAD
request omits the actual contents of the file.

"""

server_version = "SimpleHTTP/" + __version__

def do_GET(self):
"""Serve a GET request."""
f = self.send_head()
if f:
try:
self.copyfile(f, self.wfile)
finally:
f.close()

def do_HEAD(self):
"""Serve a HEAD request."""
f = self.send_head()
if f:
f.close()

def send_head(self):
"""Common code for GET and HEAD commands.

This sends the response code and MIME headers.

Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.

"""
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
parts = urlparse.urlsplit(self.path)
if not parts.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(301)
new_parts = (parts[0], parts[1], parts[2] + '/',
parts[3], parts[4])
new_url = urlparse.urlunsplit(new_parts)
self.send_header("Location", new_url)
self.end_headers()
return None
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return None
try:
self.send_response(200)
self.send_header("Content-type", ctype)
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
self.end_headers()
return f
except:
f.close()
raise

def list_directory(self, path):
"""Helper to produce a directory listing (absent index.html).

Return value is either a file object, or None (indicating an
error). In either case, the headers are sent, making the
interface the same as for send_head().

"""
try:
list = os.listdir(path)
except os.error:
self.send_error(404, "No permission to list directory")
return None
list.sort(key=lambda a: a.lower())
f = StringIO()
displaypath = cgi.escape(urllib.unquote(self.path))
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
f.write("<hr>\n<ul>\n")
for name in list:
fullname = os.path.join(path, name)
displayname = linkname = name
# Append / for directories or @ for symbolic links
if os.path.isdir(fullname):
displayname = name + "/"
linkname = name + "/"
if os.path.islink(fullname):
displayname = name + "@"
# Note: a link to a directory displays with @ and links with /
f.write('<li><a href="%s">%s</a>\n'
% (urllib.quote(linkname), cgi.escape(displayname)))
f.write("</ul>\n<hr>\n</body>\n</html>\n")
length = f.tell()
f.seek(0)
self.send_response(200)
encoding = sys.getfilesystemencoding()
self.send_header("Content-type", "text/html; charset=%s" % encoding)
self.send_header("Content-Length", str(length))
self.end_headers()
return f

def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax.

Components that mean special things to the local file system
(e.g. drive or directory names) are ignored. (XXX They should
probably be diagnosed.)

"""
# abandon query parameters
path = path.split('?',1)[0]
path = path.split('#',1)[0]
# Don't forget explicit trailing slash when normalizing. Issue17324
trailing_slash = path.rstrip().endswith('/')
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
path = os.getcwd()
for word in words:
if os.path.dirname(word) or word in (os.curdir, os.pardir):
# Ignore components that are not a simple file/directory name
continue
path = os.path.join(path, word)
if trailing_slash:
path += '/'
return path

def copyfile(self, source, outputfile):
"""Copy all data between two file objects.

The SOURCE argument is a file object open for reading
(or anything with a read() method) and the DESTINATION
argument is a file object open for writing (or
anything with a write() method).

The only reason for overriding this would be to change
the block size or perhaps to replace newlines by CRLF
-- note however that this the default server uses this
to copy binary data as well.

"""
shutil.copyfileobj(source, outputfile)

def guess_type(self, path):
"""Guess the type of a file.

Argument is a PATH (a filename).

Return value is a string of the form type/subtype,
usable for a MIME Content-type header.

The default implementation looks the file's extension
up in the table self.extensions_map, using application/octet-stream
as a default; however it would be permissible (if
slow) to look inside the data to make a better guess.

"""

base, ext = posixpath.splitext(path)
if ext in self.extensions_map:
return self.extensions_map[ext]
ext = ext.lower()
if ext in self.extensions_map:
return self.extensions_map[ext]
else:
return self.extensions_map['']

if not mimetypes.inited:
mimetypes.init() # try to read system mime.types
extensions_map = mimetypes.types_map.copy()
extensions_map.update({
'': 'application/octet-stream', # Default
'.py': 'text/plain',
'.c': 'text/plain',
'.h': 'text/plain',
})


def test(HandlerClass = SimpleHTTPRequestHandler,
ServerClass = BaseHTTPServer.HTTPServer):
BaseHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
test()

接收GET,HEAD请求
先判断是否是目录,目录不加/会重定向,默认显示目录下的index.html/index.htm文件,如果没有上述文件将目录下文件列表显示
copyfile函数调用shutil基本库,以后再介绍
利用MIME进行文件类型判断


title: 迁移cnblog博客
date: 2018-03-18
categories: cnblogs
tags: life


写博客最怕就是这种迁来迁去的,太麻烦了
还好老早就使用markdown编写,纯文本直接复制就可以了,格式不会差到哪去的,最低的保障有了可是还是很麻烦

在cnblogs后台控制面板找到 博客备份, 这个可以将所有博客导出到一个xml文件中,确实方便了很多,感谢cnblogs
后面就是各自用办法解析xml的问题了

我这里直接使用bash来解析这个xml文件
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
#
set -e

for num in {1..60}
do
title=$(echo "cat /rss/channel/item[${num}]/title/text()" |xmllint --shell cnblogs.xml | sed '1d;$d')
pubDate=$(echo "cat /rss/channel/item[${num}]/pubDate/text()"|xmllint --shell cnblogs.xml | sed '1d;$d')
datestr=$(date -d "$pubDate" +"%Y-%m-%d %T")
description=$(echo "cat /rss/channel/item[${num}]/description/text()" |xmllint --shell cnblogs.xml | sed -e '1d;$d' -e '2 s/<!\[CDATA\[//' | sed -e '$s/]]&gt;$//')

echo "---" > "${title}".md
echo "title: $title" >> "${title}".md
echo "date: $datestr" >> "${title}".md
echo "categories: cnblogs" >> "${title}".md
echo "tags: linux" >> "${title}".md
echo "---" >> "${title}".md
echo >> "${title}".md
echo "$description" >> "${title}".md
done

这里几个说明:

  1. 我之前看了博客总数是60,这里就没有从xml文件里面获取总数了
  2. 新的blog也是使用markdown,不过头部需要注明一些扩展元素(title,date,categories,tags等),这里直接写入这些信息
  3. 解析xml比较麻烦的就是正文内容了,之前的解析始终有xml的cdata字符(用–nocdata转换了特殊字符问题更大),这里用两条sed语句去除
  4. 使用的解析xml的工具是xmllint,centos(各个发行版基本都提供对应命令)可以使用yum provides xmllint查看属于哪个包 (Repo: base, Name: libxml2)
  5. 其实这里sed还是很有趣的,学海无涯沧海一粟

最后按每条博客的标题生成一个markdown文件

附注:
毕竟不是python这种可以完美处理xml,可能会对解析xml的命令感到不解
这里我主要参考:https://unix.stackexchange.com/questions/83385/parse-xml-to-get-node-value-in-bash-script
也可以参考man手册,我这里之前用–xpath提取标题,导致编码有问题,使用这种方式可以正常处理

我把测试文件附加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0"?>
<config>
<global>
<install>
<date><![CDATA[Tue, 11 Dec 2012 12:31:25 +0000]]&gt;</date>
</install>
<crypt>
<key><![CDATA[70e75d7969b900b696785f2f81ecb430]]&gt;</key>
</crypt>
<disable_local_modules>false</disable_local_modules>
<resources>
<db>
<table_prefix><![CDATA[]]&gt;</table_prefix>
</db>
<default_setup>
<connection>
<host><![CDATA[localhost]]&gt;</host>
<username><![CDATA[root]]&gt;</username>
<password><![CDATA[pass123]]&gt;</password>
<dbname><![CDATA[testdb]]&gt;</dbname>
<initStatements><![CDATA[SET NAMES utf8]]&gt;</initStatements>
<model><![CDATA[mysql4]]&gt;</model>
<type><![CDATA[pdo_mysql]]&gt;</type>
<pdoType><![CDATA[]]&gt;</pdoType>
<active>1</active>
</connection>
</default_setup>
</resources>
<session_save><![CDATA[files]]&gt;</session_save>
</global>
<admin>
<routers>
<adminhtml>
<args>
<frontName><![CDATA[admin]]&gt;</frontName>
</args>
</adminhtml>
</routers>
</admin>
</config>

参考使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
# the given XML is in file.xml
host="$(echo "cat /config/global/resources/default_setup/connection/host/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
username="$(echo "cat /config/global/resources/default_setup/connection/username/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
password="$(echo "cat /config/global/resources/default_setup/connection/password/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
dbname="$(echo "cat /config/global/resources/default_setup/connection/dbname/text()" | xmllint --nocdata --shell file.xml | sed '1d;$d')"
printf '%s\n' "host: $host" "username: $username" "password: $password" "dbname: $dbname"
}

# output
# host: localhost
# username: root
# password: pass123
# dbname: testdb

zabbix监控

通过导入/导出zabbix配置文件,我们可以将自己写好的模板等配置在网络上分享,我们也可以导入网络上分享的配置文件,配置文件有两种格式,分为xml与json,通过zabbix管理界面可以导出xml,通过zabbix api可以导出json与xml格式配置。

可导出的项目

  • host groups (只能通过zabbix api导出)
  • templates (包含所有预其直接关联的items, triggers, graphs, screens, discovery rules和link的template)
  • hosts (包含所有与它直接相关的items, triggers, graphs, discovery rules 和link的template)
  • network maps (包含所有相关图片; map export/import 从Zabbix 1.8.2开始被支持)
  • images
  • screens

导出详细说明

  • 导出为单个文件
  • Host and template从模板中link过来的实体 (items, triggers, graphs, discovery rules)不会导出,通过low-level创建的实体不会导出。但是导如之后,会重新创建link的实体。
  • 与low-level实体相关联的实体不会被导出,例如触发器。
  • web items相关的Triggers and graphs不支持导出

导入详细说明

  • 导如过程中遇到任何错误,都会停止导入
  • 支持导如xml与json格式文件,并且文件名后缀必须为.xml或者.json

XML文件基本格式

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>2.0</version>
<date>2015-02-09T05:58:54Z</date>
</zabbix_export>

xml默认头

1
<?xml version="1.0" encoding="UTF-8"?>

zabbix xml root 元素

1
<zabbix_export>

导出版本

1
<version>2.0</version>

导出日期

1
<date>2015-02-09T05:58:54Z</date>

导出模板

configuration>>templates>>勾选需要导出的模板>>左下角下拉列表选择”Export selected”,点击Go,保存xml即可。

如下是我的一个测试模板内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>2.0</version>
<date>2015-02-09T07:34:25Z</date>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<templates>
<template>
<template>A_Template_For_Discovery</template>
<name>A_Template_For_Discovery</name>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<applications>
<application>
<name>Network</name>
</application>
</applications>
<items/>
<discovery_rules>
<discovery_rule>
<name>PING</name>
<type>0</type>
<snmp_community/>
<snmp_oid/>
<key>net.if.icmpping</key>
<delay>30</delay>
<status>0</status>
<allowed_hosts/>
<snmpv3_contextname/>
<snmpv3_securityname/>
<snmpv3_securitylevel>0</snmpv3_securitylevel>
<snmpv3_authprotocol>0</snmpv3_authprotocol>
<snmpv3_authpassphrase/>
<snmpv3_privprotocol>0</snmpv3_privprotocol>
<snmpv3_privpassphrase/>
<delay_flex/>
<params/>
<ipmi_sensor/>
<authtype>0</authtype>
<username/>
<password/>
<publickey/>
<privatekey/>
<port/>
<filter>{#LOC}:^cn|^jp</filter>
<lifetime>30</lifetime>
<description/>
<item_prototypes>
<item_prototype>
<name>PING IP $1</name>
<type>3</type>
<snmp_community/>
<multiplier>1</multiplier>
<snmp_oid/>
<key>icmpping[{#IPADD},4,,,]</key>
<delay>30</delay>
<history>90</history>
<trends>365</trends>
<status>0</status>
<value_type>3</value_type>
<allowed_hosts/>
<units/>
<delta>0</delta>
<snmpv3_contextname/>
<snmpv3_securityname/>
<snmpv3_securitylevel>0</snmpv3_securitylevel>
<snmpv3_authprotocol>0</snmpv3_authprotocol>
<snmpv3_authpassphrase/>
<snmpv3_privprotocol>0</snmpv3_privprotocol>
<snmpv3_privpassphrase/>
<formula>1000</formula>
<delay_flex/>
<params/>
<ipmi_sensor/>
<data_type>0</data_type>
<authtype>0</authtype>
<username/>
<password/>
<publickey/>
<privatekey/>
<port/>
<description/>
<inventory_link>0</inventory_link>
<applications>
<application>
<name>Network</name>
</application>
</applications>
<valuemap/>
</item_prototype>
</item_prototypes>
<trigger_prototypes/>
<graph_prototypes/>
<host_prototypes/>
</discovery_rule>
</discovery_rules>
<macros/>
<templates>
<template>
<name>Template OS <a href="http://www.ttlsa.com/linux/" title="linux"target="_blank">Linux</a></name>
</template>
</templates>
<screens/>
</template>
</templates>
</zabbix_export>

与模板相关的数据都在xml里,它link的模板”Template OS Linux”并未导出。而是通过如下元素将他关联起来,下回导入还会link一次。

1
2
3
4
5
<templates>
<template>
<name>Template OS Linux</name>
</template>
</templates>

通过此方式,大家可以互相共享配置文件,提高效率。

来源:https://linux.cn/article-9394-1.html

OpenSSH 是 SSH 协议的一个实现。一般通过 scp 或 sftp 用于远程登录、备份、远程文件传输等功能。SSH能够完美保障两个网络或系统间数据传输的保密性和完整性。尽管如此,它最大的优势是使用公匙加密来进行服务器验证。时不时会出现关于 OpenSSH 零日漏洞的传言。本文将描述如何设置你的 Linux 或类 Unix 系统以提高 sshd 的安全性。

OpenSSH 默认设置

  • TCP 端口 - 22
  • OpenSSH 服务配置文件 - sshd_config (位于 /etc/ssh/)

1、 基于公匙的登录

OpenSSH 服务支持各种验证方式。推荐使用公匙加密验证。首先,使用以下 ssh-keygen 命令在本地电脑上创建密匙对:

1024 位或低于它的 DSA 和 RSA 加密是很弱的,请不要使用。当考虑 ssh 客户端向后兼容性的时候,请使用 RSA密匙代替 ECDSA 密匙。所有的 ssh 密钥要么使用 ED25519 ,要么使用 RSA,不要使用其它类型。

1
$ ssh-keygen -t key_type -b bits -C "comment"

示例:

1
2
3
$ ssh-keygen -t ed25519 -C "Login to production cluster at xyz corp"

$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_aws_$(date +%Y-%m-%d) -C "AWS key for abc corp clients"

下一步,使用 ssh-copy-id 命令安装公匙:

1
2
3
$ ssh-copy-id -i /path/to/public-key-file user@host

$ ssh-copy-id user@remote-server-ip-or-dns-name

示例:

1
$ ssh-copy-id vivek@rhel7-aws-server

提示输入用户名和密码的时候,确认基于 ssh 公匙的登录是否工作:

1
$ ssh vivek@rhel7-aws-server

OpenSSH 服务安全最佳实践

更多有关 ssh 公匙的信息,参照以下文章:

2、 禁用 root 用户登录

禁用 root 用户登录前,确认普通用户可以以 root 身份登录。例如,允许用户 vivek 使用 sudo 命令以 root 身份登录。

在 Debian/Ubuntu 系统中如何将用户 vivek 添加到 sudo 组中

允许 sudo 组中的用户执行任何命令。 将用户 vivek 添加到 sudo 组中

1
$ sudo adduser vivek sudo

使用 id 命令 验证用户组。

1
$ id vivek

在 CentOS/RHEL 系统中如何将用户 vivek 添加到 sudo 组中

在 CentOS/RHEL 和 Fedora 系统中允许 wheel 组中的用户执行所有的命令。使用 usermod 命令将用户 vivek 添加到 wheel 组中:

1
2
$ sudo usermod -aG wheel vivek
$ id vivek

测试 sudo 权限并禁用 ssh root 登录

测试并确保用户 vivek 可以以 root 身份登录执行以下命令:

1
2
3
$ sudo -i
$ sudo /etc/init.d/sshd status
$ sudo systemctl status httpd

添加以下内容到 sshd_config 文件中来禁用 root 登录:

1
2
3
4
PermitRootLogin no
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no

更多信息参见“如何通过禁用 Linux 的 ssh 密码登录来增强系统安全” 。

3、 禁用密码登录

所有的密码登录都应该禁用,仅留下公匙登录。添加以下内容到 sshd_config 文件中:

1
2
AuthenticationMethods publickey
PubkeyAuthentication yes

CentOS 6.x/RHEL 6.x 系统中老版本的 sshd 用户可以使用以下设置:

1
PubkeyAuthentication yes

4、 限制用户的 ssh 访问

默认状态下,所有的系统用户都可以使用密码或公匙登录。但是有些时候需要为 FTP 或者 email 服务创建 UNIX/Linux 用户。然而,这些用户也可以使用 ssh 登录系统。他们将获得访问系统工具的完整权限,包括编译器和诸如 Perl、Python(可以打开网络端口干很多疯狂的事情)等的脚本语言。通过添加以下内容到 sshd_config 文件中来仅允许用户 root、vivek 和 jerry 通过 SSH 登录系统:

1
AllowUsers vivek jerry

当然,你也可以添加以下内容到 sshd_config 文件中来达到仅拒绝一部分用户通过 SSH 登录系统的效果。

1
DenyUsers root saroj anjali foo

你也可以通过配置 Linux PAM 来禁用或允许用户通过 sshd 登录。也可以允许或禁止一个用户组列表通过 ssh 登录系统。

5、 禁用空密码

你需要明确禁止空密码账户远程登录系统,更新 sshd_config 文件的以下内容:

1
PermitEmptyPasswords no

6、 为 ssh 用户或者密匙使用强密码

为密匙使用强密码和短语的重要性再怎么强调都不过分。暴力破解可以起作用就是因为用户使用了基于字典的密码。你可以强制用户避开字典密码并使用约翰的开膛手工具来检测弱密码。以下是一个随机密码生成器(放到你的 ~/.bashrc 下):

1
2
3
4
5
genpasswd() {
local l=$1
[ "$l" == "" ] && l=20
tr -dc A-Za-z0-9_ < /dev/urandom | head -c ${l} | xargs
}

运行:

1
genpasswd 16

输出:

1
uw8CnDVMwC6vOKgW

7、 为 SSH 的 22端口配置防火墙

你需要更新 iptables/ufw/firewall-cmd 或 pf 防火墙配置来为 ssh 的 TCP 端口 22 配置防火墙。一般来说,OpenSSH 服务应该仅允许本地或者其他的远端地址访问。

Netfilter(Iptables) 配置

更新 /etc/sysconfig/iptables (Redhat 和其派生系统特有文件) 实现仅接受来自于 192.168.1.0/24 和 202.54.1.5/29 的连接,输入:

1
2
-A RH-Firewall-1-INPUT -s 192.168.1.0/24 -m state --state NEW -p tcp --dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -s 202.54.1.5/29 -m state --state NEW -p tcp --dport 22 -j ACCEPT

如果同时使用 IPv6 的话,可以编辑 /etc/sysconfig/ip6tables (Redhat 和其派生系统特有文件),输入:

1
-A RH-Firewall-1-INPUT -s ipv6network::/ipv6mask -m tcp -p tcp --dport 22 -j ACCEPT

ipv6network::/ipv6mask 替换为实际的 IPv6 网段。

Debian/Ubuntu Linux 下的 UFW

UFW 是 Uncomplicated FireWall 的首字母缩写,主要用来管理 Linux 防火墙,目的是提供一种用户友好的界面。输入以下命令使得系统仅允许网段 202.54.1.5/29 接入端口 22

1
$ sudo ufw allow from 202.54.1.5/29 to any port 22

更多信息请参见 “Linux:菜鸟管理员的 25 个 Iptables Netfilter 命令”。

*BSD PF 防火墙配置

如果使用 PF 防火墙 /etc/pf.conf 配置如下:

1
pass in on $ext_if inet proto tcp from {192.168.1.0/24, 202.54.1.5/29} to $ssh_server_ip port ssh flags S/SA synproxy state

8、 修改 SSH 端口和绑定 IP

ssh 默认监听系统中所有可用的网卡。修改并绑定 ssh 端口有助于避免暴力脚本的连接(许多暴力脚本只尝试端口 22)。更新文件 sshd_config 的以下内容来绑定端口 300 到 IP 192.168.1.5 和 202.54.1.5:

1
2
3
Port 300 
ListenAddress 192.168.1.5
ListenAddress 202.54.1.5

当需要接受动态广域网地址的连接时,使用主动脚本是个不错的选择,比如 fail2ban 或 denyhosts。

9、 使用 TCP wrappers (可选的)

TCP wrapper 是一个基于主机的访问控制系统,用来过滤来自互联网的网络访问。OpenSSH 支持 TCP wrappers。只需要更新文件 /etc/hosts.allow 中的以下内容就可以使得 SSH 只接受来自于 192.168.1.2 和 172.16.23.12 的连接:

1
sshd : 192.168.1.2 172.16.23.12

在 Linux/Mac OS X 和类 UNIX 系统中参见 TCP wrappers 设置和使用的常见问题

10、 阻止 SSH 破解或暴力攻击

暴力破解是一种在单一或者分布式网络中使用大量(用户名和密码的)组合来尝试连接一个加密系统的方法。可以使用以下软件来应对暴力攻击:

  • DenyHosts 是一个基于 Python SSH 安全工具。该工具通过监控授权日志中的非法登录日志并封禁原始 IP 的方式来应对暴力攻击。
    • RHEL / Fedora 和 CentOS Linux 下如何设置 DenyHosts
  • Fail2ban 是另一个类似的用来预防针对 SSH 攻击的工具。
  • sshguard 是一个使用 pf 来预防针对 SSH 和其他服务攻击的工具。
  • security/sshblock 阻止滥用 SSH 尝试登录。
  • IPQ BDB filter 可以看做是 fail2ban 的一个简化版。

11、 限制 TCP 端口 22 的传入速率(可选的)

netfilter 和 pf 都提供速率限制选项可以对端口 22 的传入速率进行简单的限制。

Iptables 示例

以下脚本将会阻止 60 秒内尝试登录 5 次以上的客户端的连入。

1
2
3
4
5
#!/bin/bash
inet_if=eth1
ssh_port=22
$IPT -I INPUT -p tcp --dport ${ssh_port} -i ${inet_if} -m state --state NEW -m recent --set
$IPT -I INPUT -p tcp --dport ${ssh_port} -i ${inet_if} -m state --state NEW -m recent --update --seconds 60 --hitcount 5

在你的 iptables 脚本中调用以上脚本。其他配置选项:

1
2
3
4
5
$IPT -A INPUT -i ${inet_if} -p tcp --dport ${ssh_port} -m state --state NEW -m limit --limit 3/min --limit-burst 3 -j ACCEPT
$IPT -A INPUT -i ${inet_if} -p tcp --dport ${ssh_port} -m state --state ESTABLISHED -j ACCEPT
$IPT -A OUTPUT -o ${inet_if} -p tcp --sport ${ssh_port} -m state --state ESTABLISHED -j ACCEPT
# another one line example
# $IPT -A INPUT -i ${inet_if} -m state --state NEW,ESTABLISHED,RELATED -p tcp --dport 22 -m limit --limit 5/minute --limit-burst 5-j ACCEPT

其他细节参见 iptables 用户手册。

*BSD PF 示例

以下脚本将限制每个客户端的连入数量为 20,并且 5 秒内的连接不超过 15 个。如果客户端触发此规则,则将其加入 abusive_ips 表并限制该客户端连入。最后 flush 关键词杀死所有触发规则的客户端的连接。

1
2
3
4
sshd_server_ip = "202.54.1.5" 
table <abusive_ips> persist
block in quick from <abusive_ips>
pass in on $ext_if proto tcp to $sshd_server_ip port ssh flags S/SA keep state (max-src-conn 20, max-src-conn-rate 15/5, overload <abusive_ips> flush)

12、 使用端口敲门(可选的)

端口敲门是通过在一组预先指定的封闭端口上生成连接尝试,以便从外部打开防火墙上的端口的方法。一旦指定的端口连接顺序被触发,防火墙规则就被动态修改以允许发送连接的主机连入指定的端口。以下是一个使用 iptables 实现的端口敲门的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$IPT -N stage1
$IPT -A stage1 -m recent --remove --name knock
$IPT -A stage1 -p tcp --dport 3456 -m recent --set --name knock2
$IPT -N stage2
$IPT -A stage2 -m recent --remove --name knock2
$IPT -A stage2 -p tcp --dport 2345 -m recent --set --name heaven
$IPT -N door
$IPT -A door -m recent --rcheck --seconds 5 --name knock2 -j stage2
$IPT -A door -m recent --rcheck --seconds 5 --name knock -j stage1
$IPT -A door -p tcp --dport 1234 -m recent --set --name knock
$IPT -A INPUT -m --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A INPUT -p tcp --dport 22 -m recent --rcheck --seconds 5 --name heaven -j ACCEPT
$IPT -A INPUT -p tcp --syn -j door

更多信息请参见:
Debian / Ubuntu: 使用 Knockd and Iptables 设置端口敲门

13、 配置空闲超时注销时长

用户可以通过 ssh 连入服务器,可以配置一个超时时间间隔来避免无人值守的 ssh 会话。 打开 sshd_config 并确保配置以下值:

1
2
ClientAliveInterval 300
ClientAliveCountMax 0

以秒为单位设置一个空闲超时时间(300秒 = 5分钟)。一旦空闲时间超过这个值,空闲用户就会被踢出会话。更多细节参见如何自动注销空闲超时的 BASH / TCSH / SSH 用户

14、 为 ssh 用户启用警示标语

更新 sshd_config 文件如下行来设置用户的警示标语:

1
Banner /etc/issue

/etc/issue 示例文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
----------------------------------------------------------------------------------------------
You are accessing a XYZ Government (XYZG) Information System (IS) that is provided for authorized use only.
By using this IS (which includes any device attached to this IS), you consent to the following conditions:
+ The XYZG routinely intercepts and monitors communications on this IS for purposes including, but not limited to,
penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM),
law enforcement (LE), and counterintelligence (CI) investigations.
+ At any time, the XYZG may inspect and seize data stored on this IS.
+ Communications using, or data stored on, this IS are not private, are subject to routine monitoring,
interception, and search, and may be disclosed or used for any XYZG authorized purpose.
+ This IS includes security measures (e.g., authentication and access controls) to protect XYZG interests--not
for your personal benefit or privacy.
+ Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching
or monitoring of the content of privileged communications, or work product, related to personal representation
or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work
product are private and confidential. See User Agreement for details.
----------------------------------------------------------------------------------------------

以上是一个标准的示例,更多的用户协议和法律细节请咨询你的律师团队。

15、 禁用 .rhosts 文件(需核实)

禁止读取用户的 ~/.rhosts~/.shosts 文件。更新 sshd_config 文件中的以下内容:

1
IgnoreRhosts yes

SSH 可以模拟过时的 rsh 命令,所以应该禁用不安全的 RSH 连接。

16、 禁用基于主机的授权(需核实)

禁用基于主机的授权,更新 sshd_config 文件的以下选项:

1
HostbasedAuthentication no

17、 为 OpenSSH 和操作系统打补丁

推荐你使用类似 yumapt-getfreebsd-update 等工具保持系统安装了最新的安全补丁。

18、 Chroot OpenSSH (将用户锁定在主目录)

默认设置下用户可以浏览诸如 /etc/bin 等目录。可以使用 chroot 或者其他专有工具如 rssh 来保护 ssh 连接。从版本 4.8p1 或 4.9p1 起,OpenSSH 不再需要依赖诸如 rssh 或复杂的 chroot(1) 等第三方工具来将用户锁定在主目录中。可以使用新的 ChrootDirectory 指令将用户锁定在其主目录,参见这篇博文

19. 禁用客户端的 OpenSSH 服务

工作站和笔记本不需要 OpenSSH 服务。如果不需要提供 ssh 远程登录和文件传输功能的话,可以禁用 sshd 服务。CentOS / RHEL 用户可以使用 yum 命令 禁用或删除 openssh-server:

1
$ sudo yum erase openssh-server

Debian / Ubuntu 用户可以使用 apt 命令/apt-get 命令 删除 openssh-server:

1
$ sudo apt-get remove openssh-server

有可能需要更新 iptables 脚本来移除 ssh 的例外规则。CentOS / RHEL / Fedora 系统可以编辑文件 /etc/sysconfig/iptables/etc/sysconfig/ip6tables。最后重启 iptables 服务:

1
2
# service iptables restart
# service ip6tables restart

20. 来自 Mozilla 的额外提示

如果使用 6.7+ 版本的 OpenSSH,可以尝试下以下设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#################[ WARNING ]########################
# Do not use any setting blindly. Read sshd_config #
# man page. You must understand cryptography to #
# tweak following settings. Otherwise use defaults #
####################################################
# Supported HostKey algorithms by order of preference.
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
# Specifies the available KEX (Key Exchange) algorithms.
KexAlgorithms [email protected],ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
# Specifies the ciphers allowed
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
#Specifies the available MAC (message authentication code) algorithms
MACs [email protected],[email protected],[email protected],hmac-sha2-512,hmac-sha2-256,[email protected]
# LogLevel VERBOSE logs user's key fingerprint on login. Needed to have a clear audit track of which key was using to log in.
LogLevel VERBOSE
# Log sftp level file access (read/write/etc.) that would not be easily logged otherwise.
Subsystem sftp /usr/lib/ssh/sftp-server -f AUTHPRIV -l INFO

使用以下命令获取 OpenSSH 支持的加密方法:

1
2
3
4
5
$ ssh -Q cipher
$ ssh -Q cipher-auth
$ ssh -Q mac
$ ssh -Q kex
$ ssh -Q key

OpenSSH安全教程查询密码和算法选择

如何测试 sshd_config 文件并重启/重新加载 SSH 服务?

在重启 sshd 前检查配置文件的有效性和密匙的完整性,运行:

1
$ sudo sshd -t

扩展测试模式:

1
$ sudo sshd -T

最后,根据系统的的版本重启 Linux 或类 Unix 系统中的 sshd 服务

1
2
3
4
$ [sudo systemctl start ssh][38] ## Debian/Ubunt Linux##
$ [sudo systemctl restart sshd.service][39] ## CentOS/RHEL/Fedora Linux##
$ doas /etc/rc.d/sshd restart ## OpenBSD##
$ sudo service sshd restart ## FreeBSD##

其他建议

  1. 使用 2FA 加强 SSH 的安全性 - 可以使用 OATH ToolkitDuoSecurity 启用多重身份验证。
  2. 基于密匙链的身份验证 - 密匙链是一个 bash 脚本,可以使得基于密匙的验证非常的灵活方便。相对于无密码密匙,它提供更好的安全性。

更多信息

  • OpenSSH 官方 项目。
  • 用户手册: sshd(8)、ssh(1)、ssh-add(1)、ssh-agent(1)。
    如果知道这里没用提及的方便的软件或者技术,请在下面的评论中分享,以帮助读者保持 OpenSSH 的安全。

关于作者

作者是 nixCraft 的创始人,一个经验丰富的系统管理员和 Linux/Unix 脚本培训师。他曾与全球客户合作,领域涉及 IT,教育,国防和空间研究以及非营利部门等多个行业。请在 TwitterFacebookGoogle+ 上关注他。

via: https://www.cyberciti.biz/tips/linux-unix-bsd-openssh-server-best-practices.html

来自: https://linux.cn/article-9365-1.html?utm_source=index&utm_medium=more

读者对象

理解 udev 背后的基本概念,学习如何写简单的规则。

要求

  • root 权限

难度

中等

约定

  • # - 要求给定的命令使用 root 权限或者直接以一个 root 用户或者使用 sudo 命令去运行。
  • $ - 要求给定的命令以一个普通的非特权用户运行。

介绍

在 GNU/Linux 系统中,虽然设备的底层支持是在内核层面处理的,但是,它们相关的事件管理是在用户空间中通过 udev 来管理的。确切地说是由 udevd 守护进程来完成的。学习如何去写规则,并应用到发生的这些事件上,将有助于我们修改系统的行为并使它适合我们的需要。

规则如何组织

udev 规则是定义在一个以 .rules 为扩展名的文件中。那些文件主要放在两个位置:/usr/lib/udev/rules.d,这个目录用于存放系统安装的规则;/etc/udev/rules.d/ 这个目录是保留给自定义规则的。

定义那些规则的文件的命名惯例是使用一个数字作为前缀(比如,50-udev-default.rules),并且以它们在目录中的词汇顺序进行处理的。在 /etc/udev/rules.d 中安装的文件,会覆盖安装在系统默认路径中的同名文件。

规则语法

如果你理解了 udev 规则的行为逻辑,它的语法并不复杂。一个规则由两个主要的节构成:match 部分,它使用一系列用逗号分隔的键定义了规则应用的条件,而 action 部分,是当条件满足时,我们执行一些动作。

测试案例

讲解可能的选项的最好方法莫过于配置一个真实的案例,因此,我们去定义一个规则作为演示,当鼠标被连接时禁用触摸板。显然,在该规则定义中提供的属性将反映我的硬件。

我们将在 /etc/udev/rules.d/99-togglemouse.rules 文件中用我们喜欢的文本编辑器来写我们的规则。一条规则定义允许跨多个行,但是,如果是这种情况,必须在一个换行字符之前使用一个反斜线(\)表示行的延续,就和 shell 脚本一样。这是我们的规则:

1
2
3
4
5
6
ACTION=="add" \
, ATTRS{idProduct}=="c52f" \
, ATTRS{idVendor}=="046d" \
, ENV{DISPLAY}=":0" \
, ENV{XAUTHORITY}="/run/user/1000/gdm/Xauthority" \
, RUN+="/usr/bin/xinput --disable 16"

我们来分析一下这个规则。

操作符

首先,对已经使用以及将要使用的操作符解释如下:

== 和 != 操作符

== 是相等操作符,而 != 是不等于操作符。通过使用它们,我们可以确认规则上应用的键是否匹配各自的值。

分配操作符 = 和 :=

= 是赋值操作符,是用于为一个键赋值。当我们想要赋值,并且想确保它不会被其它规则所覆盖,我们就需要使用 := 操作符来代替,使用这个操作符分配的值,它就不能被改变。

+= 和 -= 操作符

+=-= 操作符各自用于从一个指定的键定义的值列表中增加或者移除一个值。

我们使用的键

现在,来分析一下在这个规则中我们使用的键。首先,我们有一个 ACTION 键:通过使用它,当在一个设备上发生了特定的事件,我们将指定我们要应用的规则的具体内容。有效的值有 addremove 以及 change

然后,我们使用 ATTRS 关键字去指定一个属性去匹配。我们可以使用 udevadm info 命令去列出一个设备属性,提供它的名字或者 sysfs 路径即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
udevadm info -ap /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010/input/input39
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010/input/input39':
KERNEL=="input39"
SUBSYSTEM=="input"
DRIVER==""
ATTR{name}=="Logitech USB Receiver"
ATTR{phys}=="usb-0000:00:1d.0-1.2/input1"
ATTR{properties}=="0"
ATTR{uniq}==""
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010':
KERNELS=="0003:046D:C52F.0010"
SUBSYSTEMS=="hid"
DRIVERS=="hid-generic"
ATTRS{country}=="00"
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1':
KERNELS=="2-1.2:1.1"
SUBSYSTEMS=="usb"
DRIVERS=="usbhid"
ATTRS{authorized}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceClass}=="03"
ATTRS{bInterfaceNumber}=="01"
ATTRS{bInterfaceProtocol}=="00"
ATTRS{bInterfaceSubClass}=="00"
ATTRS{bNumEndpoints}=="01"
ATTRS{supports_autosuspend}=="1"
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2':
KERNELS=="2-1.2"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{bMaxPower}=="98mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 2"
ATTRS{bcdDevice}=="3000"
ATTRS{bmAttributes}=="a0"
ATTRS{busnum}=="2"
ATTRS{configuration}=="RQR30.00_B0009"
ATTRS{devnum}=="12"
ATTRS{devpath}=="1.2"
ATTRS{idProduct}=="c52f"
ATTRS{idVendor}=="046d"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Logitech"
ATTRS{maxchild}=="0"
ATTRS{product}=="USB Receiver"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="removable"
ATTRS{speed}=="12"
ATTRS{urbnum}=="1401"
ATTRS{version}==" 2.00"
[...]

上面截取了运行这个命令之后的输出的一部分。正如你从它的输出中看到的那样,udevadm 从我们提供的指定路径开始,并且提供了所有父级设备的信息。注意设备的属性都是以单数的形式报告的(比如,KERNEL),而它的父级是以复数形式出现的(比如,KERNELS)。父级信息可以做为规则的一部分,但是同一时间只能有一个父级可以被引用:不同父级设备的属性混合在一起是不能工作的。在上面我们定义的规则中,我们使用了一个父级设备属性:idProductidVendor

在我们的规则中接下来做的事情是,去使用 ENV 关键字:它既可以用于设置也可以用于去匹配环境变量。我们给 DISPLAYXAUTHORITY 分配值。当我们使用 X 服务器程序进行交互去设置一些需要的信息时,这些变量是非常必要的:使用 DISPLAY 变量,我们指定服务器运行在哪个机器上,用的是哪个显示和屏幕;使用 XAUTHORITY 提供了一个文件路径,其包含了 Xorg 认证和授权信息。这个文件一般位于用户的家目录中。

最后,我们使用了 RUN 字:它用于运行外部程序。非常重要:这里没有立即运行,但是一旦所有的规则被解析,将运行各种动作。在这个案例中,我们使用 xinput 实用程序去改变触摸板的状态。我不想解释这里的 xinput 的语法,它超出了本文的范围,只需要注意这个触摸板的 ID 是 16

规则设置完成之后,我们可以通过使用 udevadm test 命令去调试它。这个命令对调试非常有用,它并不真实去运行 RUN 指定的命令:

1
$ udevadm test --action="add" /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010/input/input39

我们提供给命令的是使用 --action 选项,以及设备的 sysfs 路径的模拟动作。如果没有报告错误,说明我们的规则运行的很好。要在真实的环境中去使用它,我们需要重新加载规则:

1
# udevadm control --reload

这个命令将重新加载规则文件,但是,它只对重新加载之后发生的事件有效果。

我们通过创建一个 udev 规则了解了基本的概念和逻辑,这只是 udev 规则中众多的选项和可能的设置中的一小部分。udev 手册页提供了一个详尽的列表,如果你想深入了解,请参考它。

bash history 显示时间 export HISTTIMEFORMAT='%F %T'

from: https://vms.drweb.com/virus/?i=15455134&lng=en

Linux.Siggen.180
Added to Dr.Web virus database: 2017-07-05
Virus description was added: 2017-07-05

Technical Information

To ensure autorun and distribution:

Creates or modifies the following files:

  • /etc/init.d/acpidtd
  • /etc/rc.d/rc*.d/S01acpidtd

Creates or modifies the following symlinks:

  • /etc/rc0.d/S01acpidtd
  • /etc/rc1.d/S01acpidtd
  • /etc/rc2.d/S01acpidtd
  • /etc/rc3.d/S01acpidtd
  • /etc/rc4.d/S01acpidtd
  • /etc/rc5.d/S01acpidtd
  • /etc/rc6.d/S01acpidtd

Malicious functions:

Launches itself as a daemon
Replaces the following system files:

  • /bin/ss
  • /bin/netstat

Launches processes:

  • sh -c /tmp/tmpnam_PGNdDO upgrade;sleep 1;rm -f /tmp/tmpnam_PGNdDO
  • /tmp/tmpnam_PGNdDO upgrade
  • /bin/sh ##which ss 1>/dev/null 2>&1
  • which ss
  • /bin/sh ##which ss
  • /bin/sh ##which netstat 1>/dev/null 2>&1
  • which netstat
  • /bin/sh ##chattr -i /bin/ddus-uidgen /etc/init.d/acpidtd /etc/rc.d/rc*.d/S01acpidtd /bin/ss /bin/scss /bin/netstat /bin/scnetstat 1>/dev/null 2>&1
  • chattr -i /bin/ddus-uidgen /etc/init.d/acpidtd /etc/rc.d/rc*.d/S01acpidtd /bin/ss /bin/scss /bin/netstat /bin/scnetstat
  • /bin/sh ##cp -f /tmp/tmpnam_PGNdDO /bin/ddus-uidge
  • cp -f /tmp/tmpnam_PGNdDO /bin/ddus-uidgen
  • cp -f /bin/ddus-uidgen /etc/init.d/acpidtd
  • /bin/sh ##chmod +x /bin/scss 1>/dev/null 2>&1;
  • chmod +x /bin/scss
  • ##cp -f /tmp/tmpnam_PGNdDO /bin/ss 1>/dev/null 2>&1;touch -r /bin/sh /bin/ss /bin/scss 1>/dev/null 2>&1;
  • ln -fs /etc/init.d/acpidtd /etc/rc0.d/S01acpidtd
  • cp -f /tmp/tmpnam_PGNdDO /bin/ss
  • ln -fs /etc/init.d/acpidtd /etc/rc1.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc2.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc3.d/S01acpidtd
  • touch -r /bin/sh /bin/ss /bin/scss
  • ln -fs /etc/init.d/acpidtd /etc/rc4.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc5.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc6.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc0.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc1.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc2.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc3.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc4.d/S01acpidtd
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc5.d/S01acpidtd
  • /bin/sh ##chattr +i /bin/ddus-uidgen /etc/init.d/acpidtd /etc/rc.d/rc*.d/S01acpidtd /bin/ss /bin/scss /bin/netstat /bin/scnetstat 1>/dev/null 2>&1
  • ln -fs /etc/init.d/acpidtd /etc/rc.d/rc6.d/S01acpidtd
  • chattr +i /bin/ddus-uidgen /etc/init.d/acpidtd /etc/rc.d/rc*.d/S01acpidtd /bin/ss /bin/scss /bin/netstat /bin/scnetstat
  • touch -r /bin/sh /bin/ddus-uidgen /etc/init.d/acpidtd /etc/rc.d/rc*.d/S01acpidtd
  • /bin/sh ##chmod +x /bin/scnetstat 1>/dev/null 2>&1;
  • chmod +x /bin/scnetstat
  • ##cp -f /tmp/tmpnam_PGNdDO /bin/netstat 1>/dev/null 2>&1;touch -r /bin/sh /bin/netstat /bin/scnetstat 1>/dev/null 2>&1;
  • cp -f /tmp/tmpnam_PGNdDO /bin/netstat
  • touch -r /bin/sh /bin/netstat /bin/scnetstat
  • sleep 1
  • rm -f /tmp/tmpnam_PGNdDO

Performs operations with the file system:

Modifies file access rights:

  • /tmp/tmpnam_PGNdDO
  • /bin/scss
  • /bin/scnetstat

Creates or modifies files:

  • /tmp/tmpnam_PGNdDO
  • /bin/ddus-uidgen
  • /bin/scss
  • /bin/scnetstat

Deletes files:

  • /tmp/tmpnam_PGNdDO

Network activity:

Establishes connection:

  • 16#.##.30.212:3646
  • 11#.##.33.59:2382
  • 58.##.32.135:8534
  • 22#.##.116.233:8414
  • 22#.###.214.158:9803

DNS ASK:

  • 12#.211
  • 22#.204
  • wi###o1n3.pw

from: https://yq.aliyun.com/ask/57692

wipefs是linux自带的程序,用来擦除文件系统数据,也就是下面那个人回答的。正常的wipefs,路径在/usr/bin/wipefs,如果你没有做设置,不会自启动,也不会大量占用cpu。你可以看一下是否是 /bin/wipefs 进程,如果是,应该是你的机器被黑了,这是别人在你机器上放了挖矿程序。

此程序会:
1.进行挖矿计算,大量占用cpu。
2.复制自己到/bin/wipefs,创建服务/etc/init.d/wipefs,在 /etc/rc.d 和 /etc/rc.d/rc.d 中创建链接以实现开机启动。
3.释放子程序到 /bin/ddus-uidgen,创建服务/etc/init.d/acpidtd,并在 /etc/rc.d 和 /etc/rc.d/rc.d 中创建链接以实现开机启动。
4.修改/etc/resolv.conf, 可能是为其连接矿机服务的域名做服务。
5.修改/etc/crontab, 为自己创建定时任务,每天12点与0点开始执行。(所以你会发现第二天又启动了)

你需要做的:
1.删除 /etc/crontab 中的定时任务。
2.删除以下文件:

/bin/wipefs
/etc/init.d/wipefs
/bin/ddus-uidgen
/etc/init.d/acpidtd
/etc/rc0.d/S01wipefs
/etc/rc1.d/S01wipefs
/etc/rc2.d/S01wipefs
/etc/rc3.d/S01wipefs
/etc/rc4.d/S01wipefs
/etc/rc5.d/S01wipefs
/etc/rc6.d/S01wipefs
/etc/rc.d/rc0.d/S01wipefs
/etc/rc.d/rc1.d/S01wipefs
/etc/rc.d/rc2.d/S01wipefs
/etc/rc.d/rc3.d/S01wipefs
/etc/rc.d/rc4.d/S01wipefs
/etc/rc.d/rc5.d/S01wipefs
/etc/rc.d/rc6.d/S01wipefs
/etc/rc0.d/acpidtd
/etc/rc1.d/acpidtd
/etc/rc2.d/acpidtd
/etc/rc3.d/acpidtd
/etc/rc4.d/acpidtd
/etc/rc5.d/acpidtd
/etc/rc6.d/acpidtd
/etc/rc.d/rc0.d/acpidtd
/etc/rc.d/rc1.d/acpidtd
/etc/rc.d/rc2.d/acpidtd
/etc/rc.d/rc3.d/acpidtd
/etc/rc.d/rc4.d/acpidtd
/etc/rc.d/rc5.d/acpidtd
/etc/rc.d/rc6.d/acpidtd

检查机器漏洞,ssh权限,防火墙等,避免机器再次被攻击。

来自: https://linux.cn/article-6624-1.html

Ceph 是一个将数据存储在单一分布式计算机集群上的开源软件平台。当你计划构建一个云时,你首先需要决定如何实现你的存储。开源的 Ceph 是红帽原生技术之一,它基于称为 RADOS 的对象存储系统,用一组网关 API 表示块、文件、和对象模式中的数据。由于它自身开源的特性,这种便携存储平台能在公有云和私有云上安装和使用。Ceph 集群的拓扑结构是按照备份和信息分布设计的,这种内在设计能提供数据完整性。它的设计目标就是容错、通过正确配置能运行于商业硬件和一些更高级的系统。

Ceph 能在任何 Linux 发行版上安装,但为了能正确运行,它需要最近的内核以及其它最新的库。在这篇指南中,我们会使用最小化安装的 CentOS-7.0。

系统资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
**CEPH-STORAGE**
OS: CentOS Linux 7 (Core)
RAM:1 GB
CPU:1 CPU
DISK: 20
Network: 45.79.136.163
FQDN: ceph-storage.linoxide.com
**CEPH-NODE**
OS: CentOS Linux 7 (Core)
RAM:1 GB
CPU:1 CPU
DISK: 20
Network: 45.79.171.138
FQDN: ceph-node.linoxide.com

安装前的配置

在安装 Ceph 存储之前,我们要在每个节点上完成一些步骤。第一件事情就是确保每个节点的网络已经配置好并且能相互访问。

** 配置 Hosts **

要在每个节点上配置 hosts 条目,要像下面这样打开默认的 hosts 配置文件(LCTT 译注:或者做相应的 DNS 解析)。

1
# vi /etc/hosts
1
2
45.79.136.163 ceph-storage ceph-storage.linoxide.com
45.79.171.138 ceph-node ceph-node.linoxide.com

** 安装 VMware 工具 **

工作环境是 VMWare 虚拟环境时,推荐你安装它的 open VM 工具。你可以使用下面的命令安装。

1
# yum install -y open-vm-tools

** 配置防火墙 **

如果你正在使用启用了防火墙的限制性环境,确保在你的 Ceph 存储管理节点和客户端节点中开放了以下的端口。

你必须在你的 Admin Calamari 节点开放 80、2003、以及4505-4506 端口,并且允许通过 80 号端口访问到 Ceph 或 Calamari 管理节点,以便你网络中的客户端能访问 Calamari web 用户界面。

你可以使用下面的命令在 CentOS 7 中启动并启用防火墙。

1
2
# systemctl start firewalld
# systemctl enable firewalld

运行以下命令使 Admin Calamari 节点开放上面提到的端口。

1
2
3
4
# firewall-cmd --zone=public --add-port=80/tcp --permanent
# firewall-cmd --zone=public --add-port=2003/tcp --permanent
# firewall-cmd --zone=public --add-port=4505-4506/tcp --permanent
# firewall-cmd --reload

在 Ceph Monitor 节点,你要在防火墙中允许通过以下端口。

1
# firewall-cmd --zone=public --add-port=6789/tcp --permanent

然后允许以下默认端口列表,以便能和客户端以及监控节点交互,并发送数据到其它 OSD。

1
# firewall-cmd --zone=public --add-port=6800-7300/tcp --permanent

如果你工作在非生产环境,建议你停用防火墙以及 SELinux 设置,在我们的测试环境中我们会停用防火墙以及 SELinux。

1
2
# systemctl stop firewalld
# systemctl disable firewalld

** 系统升级 **

现在升级你的系统并重启使所需更改生效。

1
2
# yum update
# shutdown -r 0

设置 Ceph 用户

现在我们会新建一个单独的 sudo 用户用于在每个节点安装 ceph-deploy工具,并允许该用户无密码访问每个节点,因为它需要在 Ceph 节点上安装软件和配置文件而不会有输入密码提示。

运行下面的命令在 ceph-storage 主机上新建有独立 home 目录的新用户。

1
2
[root@ceph-storage ~]# useradd -d /home/ceph -m ceph
[root@ceph-storage ~]# passwd ceph

节点中新建的每个用户都要有 sudo 权限,你可以使用下面展示的命令赋予 sudo 权限。

1
2
3
[root@ceph-storage ~]# echo "ceph ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/ceph
ceph ALL = (root) NOPASSWD:ALL
[root@ceph-storage ~]# sudo chmod 0440 /etc/sudoers.d/ceph

设置 SSH 密钥

现在我们会在 Ceph 管理节点生成 ssh 密钥并把密钥复制到每个 Ceph 集群节点。

在 ceph-node 运行下面的命令复制它的 ssh 密钥到 ceph-storage。

1
2
3
4
5
6
7
8
9
10
11
12
[root@ceph-node ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
5b:*:*:*:*:*:*:*:*:*:c9 root@ceph-node
The key's randomart image is:
+--[ RSA 2048]----+
1
[root@ceph-node ~]# ssh-copy-id ceph@ceph-storage

SSH key

配置 PID 数目

要配置 PID 数目的值,我们会使用下面的命令检查默认的内核值。默认情况下,是一个小的最大线程数 32768。

如下图所示通过编辑系统配置文件配置该值为一个更大的数。

更改 PID 值

配置管理节点服务器

配置并验证了所有网络后,我们现在使用 ceph 用户安装 ceph-deploy。通过打开文件检查 hosts 条目(LCTT 译注:你也可以用 DNS 解析来完成)。

1
2
3
# vim /etc/hosts
ceph-storage 45.79.136.163
ceph-node 45.79.171.138

运行下面的命令添加它的库。

1
# rpm -Uhv http://ceph.com/rpm-giant/el7/noarch/ceph-release-1-0.el7.noarch.rpm

添加 Ceph 仓仓库

或者创建一个新文件并更新 Ceph 库参数,别忘了替换你当前的 Release 和版本号。

1
[root@ceph-storage ~]# vi /etc/yum.repos.d/ceph.repo
1
2
3
4
5
6
7
[ceph-noarch]
name=Ceph noarch packages
baseurl=http://ceph.com/rpm-{ceph-release}/{distro}/noarch
enabled=1
gpgcheck=1
type=rpm-md
gpgkey=https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc

之后更新你的系统并安装 ceph-deploy 软件包。

安装 ceph-deploy 软件包

我们运行下面的命令以及 ceph-deploy 安装命令来更新系统以及最新的 ceph 库和其它软件包。

1
#yum update -y && yum install ceph-deploy -y

配置集群

使用下面的命令在 ceph 管理节点上新建一个目录并进入新目录,用于收集所有输出文件和日志。

1
2
# mkdir ~/ceph-cluster
# cd ~/ceph-cluster
1
# ceph-deploy new storage

设置 ceph 集群

如果成功执行了上面的命令,你会看到它新建了配置文件。

现在配置 Ceph 默认的配置文件,用任意编辑器打开它并在会影响你公共网络的 global 参数下面添加以下两行。

1
2
3
# vim ceph.conf
osd pool default size = 1
public network = 45.79.0.0/16

安装 Ceph

现在我们准备在和 Ceph 集群关联的每个节点上安装 Ceph。我们使用下面的命令在 ceph-storage 和 ceph-node 上安装 Ceph。

1
# ceph-deploy install ceph-node ceph-storage

安装 ceph

处理所有所需仓库和安装所需软件包会需要一些时间。

当两个节点上的 ceph 安装过程都完成后,我们下一步会通过在相同节点上运行以下命令创建监视器并收集密钥。

1
# ceph-deploy mon create-initial

Ceph 初始化监视器

设置 OSD 和 OSD 守护进程

现在我们会设置磁盘存储,首先运行下面的命令列出你所有可用的磁盘。

1
# ceph-deploy disk list ceph-storage

结果中会列出你存储节点中使用的磁盘,你会用它们来创建 OSD。让我们运行以下命令,请使用你的磁盘名称。

1
2
# ceph-deploy disk zap storage:sda
# ceph-deploy disk zap storage:sdb

为了最后完成 OSD 配置,运行下面的命令配置日志磁盘以及数据磁盘。

1
2
# ceph-deploy osd prepare storage:sdb:/dev/sda
# ceph-deploy osd activate storage:/dev/sdb1:/dev/sda1

你需要在所有节点上运行相同的命令,它会清除你磁盘上的所有东西。之后为了集群能运转起来,我们需要使用以下命令从 ceph 管理节点复制不同的密钥和配置文件到所有相关节点。

1
# ceph-deploy admin ceph-node ceph-storage

测试 Ceph

我们快完成了 Ceph 集群设置,让我们在 ceph 管理节点上运行下面的命令检查正在运行的 ceph 状态。

1
2
3
# ceph status
# ceph health
HEALTH_OK

如果你在 ceph status 中没有看到任何错误信息,就意味着你成功地在 CentOS 7 上安装了 ceph 存储集群。

总结

在这篇详细的文章中我们学习了如何使用两台安装了 CentOS 7 的虚拟机设置 Ceph 存储集群,这能用于备份或者作为用于其它虚拟机的本地存储。我们希望这篇文章能对你有所帮助。当你试着安装的时候记得分享你的经验。

转自:https://linux.cn/article-8900-1.html?utm_source=index&utm_medium=moremore

目录

  • break – 在指定的行或函数处设置断点,缩写为 b
  • info breakpoints – 打印未删除的所有断点,观察点和捕获点的列表,缩写为 i b
  • disable – 禁用断点,缩写为 dis
  • enable – 启用断点
  • clear – 清除指定行或函数处的断点
  • delete – 删除断点,缩写为 d
  • tbreak – 设置临时断点,参数同 break,但在程序第一次停住后会被自动删除
  • watch – 为表达式(或变量)设置观察点,当表达式(或变量)的值有变化时,暂停程序执行
  • step – 单步跟踪,如果有函数调用,会进入该函数,缩写为 s
  • reverse-step – 反向单步跟踪,如果有函数调用,会进入该函数
  • next – 单步跟踪,如果有函数调用,不会进入该函数,缩写为 n
  • reverse-next – 反向单步跟踪,如果有函数调用,不会进入该函数
  • return – 使选定的栈帧返回到其调用者
  • finish – 执行直到选择的栈帧返回,缩写为 fin
  • until – 执行直到达到当前栈帧中当前行后的某一行(用于跳过循环、递归函数调用),缩写为 u
  • continue – 恢复程序执行,缩写为 c
  • print – 打印表达式 EXP 的值,缩写为 p
  • x – 查看内存
  • display – 每次程序停止时打印表达式 EXP 的值(自动显示)
  • info display – 打印早先设置为自动显示的表达式列表
  • disable display – 禁用自动显示
  • enable display – 启用自动显示
  • undisplay – 删除自动显示项
  • help – 打印命令列表(带参数时查找命令的帮助),缩写为 h
  • attach – 挂接到已在运行的进程来调试
  • run – 启动被调试的程序,缩写为 r
  • backtrace – 查看程序调用栈的信息,缩写为 bt
  • ptype – 打印类型 TYPE 的定义

break

使用 break 命令(缩写 b)来设置断点。

用法:

  • break 当不带参数时,在所选栈帧中执行的下一条指令处设置断点。
  • break <function-name> 在函数体入口处打断点,在 C++ 中可以使用 class::function 或 function(type, ...) 格式来指定函数名。
  • break <line-number> 在当前源码文件指定行的开始处打断点。
  • break -N break +N 在当前源码行前面或后面的 N 行开始处打断点,N 为正整数。
  • break <filename:linenum> 在源码文件 filenamelinenum 行处打断点。
  • break <filename:function> 在源码文件 filenamefunction 函数入口处打断点。
  • break <address> 在程序指令的地址处打断点。
  • break ... if <cond> 设置条件断点,... 代表上述参数之一(或无参数),cond 为条件表达式,仅在 cond 值非零时暂停程序执行。

详见官方文档

info breakpoints

查看断点,观察点和捕获点的列表。

用法:

  • info breakpoints [list...]
  • info break [list...]
  • list... 用来指定若干个断点的编号(可省略),可以是 21-32 5 等。

disable

禁用一些断点。参数是用空格分隔的断点编号。要禁用所有断点,不加参数。

禁用的断点不会被忘记,但直到重新启用才有效。

用法:

  • disable [breakpoints] [list...]
  • breakpointsdisable 的子命令(可省略),list...info breakpoints 中的描述。
    详见官方文档

enable

启用一些断点。给出断点编号(以空格分隔)作为参数。没有参数时,所有断点被启用。

用法:

  • enable [breakpoints] [list...] 启用指定的断点(或所有定义的断点)。
  • enable [breakpoints] once list... 临时启用指定的断点。GDB 在停止您的程序后立即禁用这些断点。
  • enable [breakpoints] delete list... 使指定的断点启用一次,然后删除。一旦您的程序停止,GDB 就会删除这些断点。等效于用 tbreak 设置的断点。

breakpointsdisable 中的描述。

详见官方文档

clear

在指定行或函数处清除断点。参数可以是行号,函数名称或 * 跟一个地址。

用法:

  • clear 当不带参数时,清除所选栈帧在执行的源码行中的所有断点。
  • clear <function>, clear <filename:function> 删除在命名函数的入口处设置的任何断点。
  • clear <linenum>, clear <filename:linenum> 删除在指定的文件指定的行号的代码中设置的任何断点。
  • clear <address> 清除指定程序指令的地址处的断点。

详见官方文档

delete

删除一些断点或自动显示表达式。参数是用空格分隔的断点编号。要删除所有断点,不加参数。

用法: delete [breakpoints] [list...]

详见官方文档

tbreak

设置临时断点。参数形式同 break 一样。

除了断点是临时的之外,其他同 break 一样,所以在命中时会被删除。

详见官方文档

watch

为表达式设置观察点。

用法: watch [-l|-location] <expr> 每当一个表达式的值改变时,观察点就会暂停程序执行。

如果给出了 -l 或者 -location,则它会对 expr 求值并观察它所指向的内存。例如,watch *(int *)0x12345678 将在指定的地址处观察一个 4 字节的区域(假设 int 占用 4 个字节)。

详见官方文档

step

单步执行程序,直到到达不同的源码行。

用法: step [N] 参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

警告:如果当控制在没有调试信息的情况下编译的函数中使用 step 命令,则执行将继续进行,直到控制到达具有调试信息的函数。 同样,它不会进入没有调试信息编译的函数。

要执行没有调试信息的函数,请使用 stepi 命令,详见后文。

详见官方文档

reverse-step

反向单步执行程序,直到到达另一个源码行的开头。

用法: reverse-step [N] 参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

详见官方文档

next

单步执行程序,执行完子程序调用。

用法: next [N]

step 不同,如果当前的源代码行调用子程序,则此命令不会进入子程序,而是将其视为单个源代码行,继续执行。

详见官方文档

reverse-next

反向步进程序,执行完子程序调用。

用法: reverse-next [N]

如果要执行的源代码行调用子程序,则此命令不会进入子程序,调用被视为一个指令。

参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

详见官方文档

return

您可以使用 return 命令取消函数调用的执行。如果你给出一个表达式参数,它的值被用作函数的返回值。

用法: return <expression>expression 的值作为函数的返回值并使函数直接返回。

详见官方文档

finish

执行直到选定的栈帧返回。

用法: finish 返回后,返回的值将被打印并放入到值历史记录中。

详见官方文档

until

执行直到程序到达当前栈帧中当前行之后(与 break 命令相同的参数)的源码行。此命令用于通过一个多次的循环,以避免单步执行。

用法:until <location>u <location> 继续运行程序,直到达到指定的位置,或者当前栈帧返回。

详见官方文档

continue

在信号或断点之后,继续运行被调试的程序。

用法: continue [N] 如果从断点开始,可以使用数字 N 作为参数,这意味着将该断点的忽略计数设置为 N - 1(以便断点在第 N 次到达之前不会中断)。如果启用了非停止模式(使用 show non-stop 查看),则仅继续当前线程,否则程序中的所有线程都将继续。

详见官方文档

print

求值并打印表达式 EXP 的值。可访问的变量是所选栈帧的词法环境,以及范围为全局或整个文件的所有变量。

用法:

print [expr]print /f [expr] expr 是一个(在源代码语言中的)表达式。
默认情况下,expr 的值以适合其数据类型的格式打印;您可以通过指定 /f 来选择不同的格式,其中 f 是一个指定格式的字母;详见输出格式

如果省略 expr,GDB 再次显示最后一个值。

要以每行一个成员带缩进的格式打印结构体变量请使用命令 set print pretty on,取消则使用命令 set print pretty off

可使用命令 show print 查看所有打印的设置。

详见官方文档

x

检查内存。

用法: x/nfu <addr>x <addr> nfu 都是可选参数,用于指定要显示的内存以及如何格式化。addr 是要开始显示内存的地址的表达式。

n 重复次数(默认值是 1),指定要显示多少个单位(由 u 指定)的内存值。

f 显示格式(初始默认值是 x),显示格式是 print('x','d','u','o','t','a','c','f','s') 使用的格式之一,再加 i(机器指令)。

u 单位大小,b 表示单字节,h 表示双字节,w 表示四字节,g 表示八字节。

例如:

x/3uh 0x54320 表示从地址 0x54320 开始以无符号十进制整数的格式,双字节为单位来显示 3 个内存值。

x/16xb 0x7f95b7d18870 表示从地址 0x7f95b7d18870 开始以十六进制整数的格式,单字节为单位显示 16 个内存值。

详见官方文档

display

每次程序暂停时,打印表达式 EXP 的值。

用法: display <expr>, display/fmt <expr>display/fmt <addr> fmt 用于指定显示格式。像 print 命令里的 /f 一样。

对于格式 is,或者包括单位大小或单位数量,将表达式 addr 添加为每次程序停止时要检查的内存地址。

详见官方文档

info display

打印自动显示的表达式列表,每个表达式都带有项目编号,但不显示其值。

包括被禁用的表达式和不能立即显示的表达式(当前不可用的自动变量)。

undisplay

取消某些表达式在程序暂停时的自动显示。参数是表达式的编号(使用 info display 查询编号)。不带参数表示取消所有自动显示表达式。

delete display 具有与此命令相同的效果。

disable display

禁用某些表达式在程序暂停时的自动显示。禁用的显示项目不会被自动打印,但不会被忘记。 它可能稍后再次被启用。

参数是表达式的编号(使用 info display 查询编号)。不带参数表示禁用所有自动显示表达式。

enable display

启用某些表达式在程序暂停时的自动显示。

参数是重新显示的表达式的编号(使用 info display 查询编号)。不带参数表示启用所有自动显示表达式。

help

打印命令列表。

您可以使用不带参数的 help(缩写为 h)来显示命令的类别名的简短列表。

使用 help <class> 您可以获取该类中的各个命令的列表。使用 help <command> 显示如何使用该命令。

详见官方文档

attach

挂接到 GDB 之外的进程或文件。该命令可以将进程 ID 或设备文件作为参数。

对于进程 ID,您必须具有向进程发送信号的权限,并且必须具有与调试器相同的有效的 uid。

用法: attach <process-id> GDB 在安排调试指定的进程之后做的第一件事是暂停该进程。

无论是通过 attach 命令挂接的进程还是通过 run 命令启动的进程,您都可以使用的 GDB 命令来检查和修改挂接的进程。

详见官方文档

run

启动被调试的程序。

可以直接指定参数,也可以用 set args 设置(启动所需的)参数。

例如: run arg1 arg2 ... 等效于

1
2
set args arg1 arg2 ...
run

还允许使用 ><>> 进行输入和输出重定向。

详见官方文档

backtrace

打印整体栈帧信息。

  • bt 打印整体栈帧信息,每个栈帧一行。
  • bt n 类似于上,但只打印最内层的 n 个栈帧。
  • bt -n 类似于上,但只打印最外层的 n 个栈帧。
  • bt full n 类似于 bt n,还打印局部变量的值。

whereinfo stack(缩写 info s) 是 backtrace 的别名。调用栈信息类似如下:

1
2
3
4
5
6
7
8
9
(gdb) where
#0 vconn_stream_run (vconn=0x99e5e38) at lib/vconn-stream.c:232
#1 0x080ed68a in vconn_run (vconn=0x99e5e38) at lib/vconn.c:276
#2 0x080dc6c8 in rconn_run (rc=0x99dbbe0) at lib/rconn.c:513
#3 0x08077b83 in ofconn_run (ofconn=0x99e8070, handle_openflow=0x805e274 <handle_openflow>) at ofproto/connmgr.c:1234
#4 0x08075f92 in connmgr_run (mgr=0x99dc878, handle_openflow=0x805e274 <handle_openflow>) at ofproto/connmgr.c:286
#5 0x08057d58 in ofproto_run (p=0x99d9ba0) at ofproto/ofproto.c:1159
#6 0x0804f96b in bridge_run () at vswitchd/bridge.c:2248
#7 0x08054168 in main (argc=4, argv=0xbf8333e4) at vswitchd/ovs-vswitchd.c:125

详见官方文档

ptype

打印类型 TYPE 的定义。

用法: ptype[/FLAGS] TYPE-NAME | EXPRESSION

参数可以是由 typedef 定义的类型名, 或者 struct STRUCT-TAG 或者 class CLASS-NAME 或者 union UNION-TAG 或者 enum ENUM-TAG

根据所选的栈帧的词法上下文来查找该名字。

类似的命令是 whatis,区别在于 whatis 不展开由 typedef 定义的数据类型,而 ptype 会展开,举例如下:

1
2
3
4
5
6
7
8
9
/* 类型声明与变量定义 */
typedef double real_t;
struct complex {
real_t real;
double imag;
};
typedef struct complex complex_t;
complex_t var;
real_t *real_pointer_var;

这两个命令给出了如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) whatis var
type = complex_t
(gdb) ptype var
type = struct complex {
real_t real;
double imag;
}
(gdb) whatis complex_t
type = struct complex
(gdb) whatis struct complex
type = struct complex
(gdb) ptype struct complex
type = struct complex {
real_t real;
double imag;
}
(gdb) whatis real_pointer_var
type = real_t *
(gdb) ptype real_pointer_var
type = double *

详见官方文档

参考资料

Debugging with GDB

转自: https://mritd.me/2018/01/07/kubernetes-tls-bootstrapping-note/

前段时间撸了一会 Kubernetes 官方文档,在查看 TLS bootstrapping 这块是发现已经跟 1.4 的时候完全不一样了;目前所有搭建文档也都保留着 1.4 时代的配置,在看完文档后发现目前配置有很多问题,同时也埋下了 隐藏炸弹,这个问题可能会在一年后爆发…..后果就是集群 node 全部掉线;所以仔细的撸了一下这个文档,从元旦到写此文章的时间都在测试这个 TLS bootstrapping,以下记录一下这次的成果

阅读本文章前,请先阅读一下本文参考的相关文档:

TLS bootstrapping
Kubelet Server Certificate Bootstrap & Rotation
Using RBAC Authorization

一、TLS bootstrapping 简介

Kubernetes 在 1.4 版本(我记着是)推出了 TLS bootstrapping 功能;这个功能主要解决了以下问题:

当集群开启了 TLS 认证后,每个节点的 kubelet 组件都要使用由 apiserver 使用的 CA 签发的有效证书才能与 apiserver 通讯;此时如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情;TLS bootstrapping 功能就是让 kubelet 先使用一个预定的低权限用户连接到 apiserver,然后向 apiserver 申请证书,kubelet 的证书由 apiserver 动态签署;在配合 RBAC 授权模型下的工作流程大致如下所示(不完整,下面细说)

tls_bootstrapping

二、TLS bootstrapping 相关术语

2.1、kubelet server
在官方 TLS bootstrapping 文档中多次提到过 kubelet server 这个东西;在经过翻阅大量文档以及 TLS bootstrapping 设计文档后得出,kubelet server * 指的应该是 kubelet 的 10250 端口 *;

  • kubelet 组件在工作时,采用主动的查询机制,即定期请求 apiserver 获取自己所应当处理的任务,如哪些 pod 分配到了自己身上,从而去处理这些任务;同时 kubelet 自己还会暴露出两个本身 api 的端口,用于将自己本身的私有 api 暴露出去,这两个端口分别是 10250 与 10255;对于 10250 端口,kubelet 会在其上采用 TLS 加密以提供适当的鉴权功能;对于 10255 端口,kubelet 会以只读形式暴露组件本身的私有 api,并且不做鉴权处理 *

  • 总结一下,就是说 kubelet 上实际上有两个地方用到证书,一个是用于与 API server 通讯所用到的证书,另一个是 kubelet 的 10250 私有 api 端口需要用到的证书 *

2.2、CSR 请求类型
kubelet 发起的 CSR 请求都是由 controller manager 来做实际签署的,对于 controller manager 来说,TLS bootstrapping 下 kubelet 发起的 CSR 请求大致分为以下三种

  • nodeclient: kubelet 以 O=system:nodes 和 CN=system:node:(node name) 形式发起的 CSR 请求
  • selfnodeclient: kubelet client renew 自己的证书发起的 CSR 请求(与上一个证书就有相同的 O 和 CN)
  • selfnodeserver: kubelet server renew 自己的证书发起的 CSR 请求
  • 大白话加自己测试得出的结果: nodeclient 类型的 CSR 仅在第一次启动时会产生,selfnodeclient 类型的 CSR 请求实际上就是 kubelet renew 自己作为 client 跟 apiserver 通讯时使用的证书产生的,selfnodeserver 类型的 CSR 请求则是 kubelet 首次申请或后续 renew 自己的 10250 api 端口证书时产生的 *

三、TLS bootstrapping 具体引导过程

3.1、Kubernetes TLS 与 RBAC 认证
在说具体的引导过程之前先谈一下 TLS 和 RBAC,因为这两个事不整明白下面的都不用谈;

  • TLS 作用
    众所周知 TLS 的作用就是对通讯加密,防止中间人窃听;同时如果证书不信任的话根本就无法与 apiserver 建立连接,更不用提有没有权限向 apiserver 请求指定内容

  • RBAC 作用
    当 TLS 解决了通讯问题后,那么权限问题就应由 RBAC 解决(可以使用其他权限模型,如 ABAC);RBAC 中规定了一个用户或者用户组(subject)具有请求哪些 api 的权限;* 在配合 TLS 加密的时候,实际上 apiserver 读取客户端证书的 CN 字段作为用户名,读取 O 字段作为用户组 *

从以上两点上可以总结出两点: 第一,想要与 apiserver 通讯就必须采用由 apiserver CA 签发的证书,这样才能形成信任关系,建立 TLS 连接;第二,可以通过证书的 CN、O 字段来提供 RBAC 所需的用户与用户组

3.2、kubelet 首次启动流程
看完上面的介绍,不知道有没有人想过,既然 TLS bootstrapping 功能是让 kubelet 组件去 apiserver 申请证书,然后用于连接 apiserver;* 那么第一次启动时没有证书如何连接 apiserver *?

这个问题实际上可以去查看一下 bootstrap.kubeconfig 和 token.csv 得到答案: * 在 apiserver 配置中指定了一个 token.csv 文件,该文件中是一个预设的用户配置;同时该用户的 Token 和 apiserver 的 CA 证书被写入了 kubelet 所使用的 bootstrap.kubeconfig 配置文件中;这样在首次请求时,kubelet 使用 bootstrap.kubeconfig 中的 apiserver CA 证书来与 apiserver 建立 TLS 通讯,使用 bootstrap.kubeconfig 中的用户 Token 来向 apiserver 声明自己的 RBAC 授权身份 *,如下图所示

first_request

在有些用户首次启动时,可能与遇到 kubelet 报 401 无权访问 apiserver 的错误;*
这是因为在默认情况下,kubelet 通过 bootstrap.kubeconfig 中的预设用户 Token 声明了自己的身份,然后创建 CSR 请求;但是不要忘记这个用户在我们不处理的情况下他没任何权限的,包括创建 CSR 请求;所以需要如下命令创建一个 ClusterRoleBinding,将预设用户 kubelet-bootstrap 与内置的 ClusterRole system:node-bootstrapper 绑定到一起,使其能够发起 CSR 请求 *

1
2
3
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap

3.3、手动签发证书
在 kubelet 首次启动后,如果用户 Token 没问题,并且 RBAC 也做了相应的设置,那么此时在集群内应该能看到 kubelet 发起的 CSR 请求

bootstrap_csr

出现 CSR 请求后,可以使用 kubectl 手动签发(允许) kubelet 的证书

bootstrap_approve_crt

当成功签发证书后,目标节点的 kubelet 会将证书写入到 –cert-dir= 选项指定的目录中;注意此时如果不做其他设置应当生成四个文件

bootstrap_crt

而 kubelet 与 apiserver 通讯所使用的证书为 kubelet-client.crt,剩下的 kubelet.crt 将会被用于 kubelet server(10250) 做鉴权使用;注意,此时 kubelet.crt 这个证书是个独立于 apiserver CA 的自签 CA,并且删除后 kubelet 组件会重新生成它

四、TLS bootstrapping 证书自动续期

单独把这部分拿出来写,是因为个人觉得上面已经有点乱了;这部分实际上更复杂,只好单独写一下了,因为这部分涉及的东西比较多,所以也不想草率的几笔带过

4.1、RBAC 授权
首先…首先好几次了…嗯,就是说 kubelet 所发起的 CSR 请求是由 controller manager 签署的;如果想要是实现自动续期,就需要让 controller manager 能够在 kubelet 发起证书请求的时候自动帮助其签署证书;那么 controller manager 不可能对所有的 CSR 证书申请都自动签署,这时候就需要配置 RBAC 规则,保证 controller manager 只对 kubelet 发起的特定 CSR 请求自动批准即可;在 TLS bootstrapping 官方文档中,针对上面 2.2 章节提出的 3 种 CSR 请求分别给出了 3 种对应的 ClusterRole,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# A ClusterRole which instructs the CSR approver to approve a user requesting
# node client credentials.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-client-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/nodeclient"]
verbs: ["create"]
---
# A ClusterRole which instructs the CSR approver to approve a node renewing its
# own client credentials.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-client-renewal-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeclient"]
verbs: ["create"]
---
# A ClusterRole which instructs the CSR approver to approve a node requesting a
# serving cert matching its client cert.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-server-renewal-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeserver"]
verbs: ["create"]

RBAC 中 ClusterRole 只是描述或者说定义一种集群范围内的能力,这三个 ClusterRole 在 1.7 之前需要自己手动创建,在 1.8 后 apiserver 会自动创建前两个(1.8 以后名称有改变,自己查看文档);以上三个 ClusterRole 含义如下

  • approve-node-client-csr: 具有自动批准 nodeclient 类型 CSR 请求的能力
  • approve-node-client-renewal-csr: 具有自动批准 selfnodeclient 类型 CSR 请求的能力
  • approve-node-server-renewal-csr: 具有自动批准 selfnodeserver 类型 CSR 请求的能力

所以,如果想要 kubelet 能够自动续期,那么就应当将适当的 ClusterRole 绑定到 kubelet 自动续期时所所采用的用户或者用户组身上

4.2、自动续期下的引导过程
在自动续期下引导过程与单纯的手动批准 CSR 有点差异,具体的引导流程地址如下

  • kubelet 读取 bootstrap.kubeconfig,使用其 CA 与 Token 向 apiserver 发起第一次 CSR 请求(nodeclient)
  • apiserver 根据 RBAC 规则自动批准首次 CSR 请求(approve-node-client-csr),并下发证书(kubelet-client.crt)
  • kubelet 使用刚刚签发的证书(O=system:nodes, CN=system:node:NODE_NAME)与 apiserver 通讯,并发起申请 10250 server 所使用证书的 CSR 请求
  • apiserver 根据 RBAC 规则自动批准 kubelet 为其 10250 端口申请的证书(kubelet-server-current.crt)
  • 证书即将到期时,kubelet 自动向 apiserver 发起用于与 apiserver 通讯所用证书的 renew CSR 请求和 renew 本身 10250 端口所用证书的 CSR 请求
  • apiserver 根据 RBAC 规则自动批准两个证书
  • kubelet 拿到新证书后关闭所有连接,reload 新证书,以后便一直如此

从以上流程我们可以看出,我们如果要创建 RBAC 规则,则至少能满足四种情况:

  • 自动批准 kubelet 首次用于与 apiserver 通讯证书的 CSR 请求(nodeclient)
  • 自动批准 kubelet 首次用于 10250 端口鉴权的 CSR 请求(实际上这个请求走的也是 selfnodeserver 类型 CSR)
  • 自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求(selfnodeclient)
  • 自动批准 kubelet 后续 renew 用于 10250 端口鉴权的 CSR 请求(selfnodeserver)
    基于以上四种情况,我们需要创建 3 个 ClusterRoleBinding,创建如下
1
2
3
4
5
6
7
8
# 自动批准 kubelet 的首次 CSR 请求(用于与 apiserver 通讯的证书)
kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=approve-node-client-csr --group=system:bootstrappers

# 自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=approve-node-client-renewal-csr --group=system:nodes

# 自动批准 kubelet 发起的用于 10250 端口鉴权证书的 CSR 请求(包括后续 renew)
kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes

4.3、开启自动续期
在 1.7 后,kubelet 启动时增加 --feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true 选项,则 kubelet 在证书即将到期时会自动发起一个 renew 自己证书的 CSR 请求;同时 controller manager 需要在启动时增加 --feature-gates=RotateKubeletServerCertificate=true 参数,再配合上面创建好的 ClusterRoleBinding,kubelet client 和 kubelet server 证才书会被自动签署;

注意,1.7 版本设置自动续期参数后,新的 renew 请求不会立即开始,而是在证书总有效期的 70%~90% 的时间时发起;而且经测试 1.7 版本即使自动签发了证书,kubelet 在不重启的情况下不会重新应用新证书;在 1.8 后 kubelet 组件在增加一个 –rotate-certificates 参数后,kubelet 才会自动重载新证书

4.3、证书过期问题
需要重复强调一个问题是: TLS bootstrapping 时的证书实际是由 kube-controller-manager 组件来签署的,也就是说证书有效期是 kube-controller-manager 组件控制的;所以在 1.7 版本以后(我查文档发现的从1.7开始有) kube-controller-manager 组件提供了一个 --experimental-cluster-signing-duration 参数来设置签署的证书有效时间;默认为 8760h0m0s,将其改为 87600h0m0s 即 10 年后再进行 TLS bootstrapping 签署证书即可。

五、TLS bootstrapping 总结以及详细操作

5.1、主要流程细节
kubelet 首次启动通过加载 bootstrap.kubeconfig 中的用户 Token 和 apiserver CA 证书发起首次 CSR 请求,这个 Token 被预先内置在 apiserver 节点的 token.csv 中,其身份为 kubelet-bootstrap 用户和 system:bootstrappers 用户组;想要首次 CSR 请求能成功(成功指的是不会被 apiserver 401 拒绝),则需要先将 kubelet-bootstrap 用户和 system:node-bootstrapper 内置 ClusterRole 绑定;

对于首次 CSR 请求可以手动批准,也可以将 system:bootstrappers 用户组与 approve-node-client-csr ClusterRole 绑定实现自动批准(1.8 之前这个 ClusterRole 需要手动创建,1.8 后 apiserver 自动创建,并更名为 system:certificates.k8s.io:certificatesigningrequests:nodeclient)

默认签署的的证书只有 1 年有效期,如果想要调整证书有效期可以通过设置 kube-controller-manager 的 –experimental-cluster-signing-duration 参数实现,该参数默认值为 8760h0m0s

对于证书自动续签,需要通过协调两个方面实现;第一,想要 kubelet 在证书到期后自动发起续期请求,则需要在 kubelet 启动时增加 –feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true 来实现;第二,想要让 controller manager 自动批准续签的 CSR 请求需要在 controller manager 启动时增加 –feature-gates=RotateKubeletServerCertificate=true 参数,并绑定对应的 RBAC 规则;同时需要注意的是 1.7 版本的 kubelet 自动续签后需要手动重启 kubelet 以使其重新加载新证书,而 1.8 后只需要在 kublet 启动时附带 –rotate-certificates 选项就会自动重新加载新证书

5.2、证书及配置文件作用
token.csv
该文件为一个用户的描述文件,基本格式为 Token,用户名,UID,用户组;这个文件在 apiserver 启动时被 apiserver 加载,然后就相当于在集群内创建了一个这个用户;接下来就可以用 RBAC 给他授权;持有这个用户 Token 的组件访问 apiserver 的时候,apiserver 根据 RBAC 定义的该用户应当具有的权限来处理相应请求

bootstarp.kubeconfig
该文件中内置了 token.csv 中用户的 Token,以及 apiserver CA 证书;kubelet 首次启动会加载此文件,使用 apiserver CA 证书建立与 apiserver 的 TLS 通讯,使用其中的用户 Token 作为身份标识像 apiserver 发起 CSR 请求

kubelet-client.crt
该文件在 kubelet 完成 TLS bootstrapping 后生成,此证书是由 controller manager 签署的,此后 kubelet 将会加载该证书,用于与 apiserver 建立 TLS 通讯,同时使用该证书的 CN 字段作为用户名,O 字段作为用户组向 apiserver 发起其他请求

kubelet.crt
该文件在 kubelet 完成 TLS bootstrapping 后并且没有配置 –feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该文件为一个独立于 apiserver CA 的自签 CA 证书,有效期为 1 年;被用作 kubelet 10250 api 端口

kubelet-server.crt
该文件在 kubelet 完成 TLS bootstrapping 后并且配置了 –feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该证书由 apiserver CA 签署,默认有效期同样是 1 年,被用作 kubelet 10250 api 端口鉴权

kubelet-client-current.pem
这是一个软连接文件,当 kubelet 配置了 –feature-gates=RotateKubeletClientCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-client-时间戳.pem;kubelet-client-current.pem 文件则始终软连接到最新的真实证书文件,除首次启动外,kubelet 一直会使用这个证书同 apiserver 通讯

kubelet-server-current.pem
同样是一个软连接文件,当 kubelet 配置了 –feature-gates=RotateKubeletServerCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-server-时间戳.pem;kubelet-server-current.pem 文件则始终软连接到最新的真实证书文件,该文件将会一直被用于 kubelet 10250 api 端口鉴权

5.3、1.7 TLS bootstrapping 配置
apiserver 预先放置 token.csv,内容样例如下

1
6df3c701f979cee17732c30958745947,kubelet-bootstrap,10001,"system:bootstrappers"

允许 kubelet-bootstrap 用户创建首次启动的 CSR 请求

1
2
3
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap

配置 kubelet 自动续期,RotateKubeletClientCertificate 用于自动续期 kubelet 连接 apiserver 所用的证书(kubelet-client-xxxx.pem),RotateKubeletServerCertificate 用于自动续期 kubelet 10250 api 端口所使用的证书(kubelet-server-xxxx.pem)

1
2
3
4
5
6
7
8
9
10
11
12
KUBELET_ARGS="--cgroup-driver=cgroupfs \
--cluster-dns=10.254.0.2 \
--resolv-conf=/etc/resolv.conf \
--experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \
--feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig \
--fail-swap-on=false \
--cert-dir=/etc/kubernetes/ssl \
--cluster-domain=cluster.local. \
--hairpin-mode=promiscuous-bridge \
--serialize-image-pulls=false \
--pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

配置 controller manager 自动批准相关 CSR 请求,如果不配置 –feature-gates=RotateKubeletServerCertificate=true 参数,则即使配置了相关的 RBAC 规则,也只会自动批准 kubelet client 的 renew 请求

1
2
3
4
5
6
7
8
9
10
11
12
KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 \
--service-cluster-ip-range=10.254.0.0/16 \
--feature-gates=RotateKubeletServerCertificate=true \
--cluster-name=kubernetes \
--cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem \
--cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \
--service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \
--root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem \
--leader-elect=true \
--node-monitor-grace-period=40s \
--node-monitor-period=5s \
--pod-eviction-timeout=5m0s"

创建自动批准相关 CSR 请求的 ClusterRole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# A ClusterRole which instructs the CSR approver to approve a user requesting
# node client credentials.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-client-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/nodeclient"]
verbs: ["create"]
---
# A ClusterRole which instructs the CSR approver to approve a node renewing its
# own client credentials.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-client-renewal-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeclient"]
verbs: ["create"]
---
# A ClusterRole which instructs the CSR approver to approve a node requesting a
# serving cert matching its client cert.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-server-renewal-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeserver"]
verbs: ["create"]

将 ClusterRole 绑定到适当的用户组,以完成自动批准相关 CSR 请求

1
2
3
4
5
6
7
8
# 自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=approve-node-client-csr --group=system:bootstrappers

# 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=approve-node-client-renewal-csr --group=system:nodes

# 自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求
kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes

一切就绪后启动 kubelet 组件即可,不过需要注意的是 1.7 版本 kubelet 不会自动重载 renew 的证书,需要自己手动重启

5.4、1.8 TLS bootstrapping 配置
apiserver 预先放置 token.csv,内容样例如下

1
6df3c701f979cee17732c30958745947,kubelet-bootstrap,10001,"system:bootstrappers"

允许 kubelet-bootstrap 用户创建首次启动的 CSR 请求

1
2
3
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap

配置 kubelet 自动续期,RotateKubeletClientCertificate 用于自动续期 kubelet 连接 apiserver 所用的证书(kubelet-client-xxxx.pem),RotateKubeletServerCertificate 用于自动续期 kubelet 10250 api 端口所使用的证书(kubelet-server-xxxx.pem),–rotate-certificates 选项使得 kubelet 能够自动重载新证书

1
2
3
4
5
6
7
8
9
10
11
12
13
KUBELET_ARGS="--cgroup-driver=cgroupfs \
--cluster-dns=10.254.0.2 \
--resolv-conf=/etc/resolv.conf \
--experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \
--feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true \
--rotate-certificates \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig \
--fail-swap-on=false \
--cert-dir=/etc/kubernetes/ssl \
--cluster-domain=cluster.local. \
--hairpin-mode=promiscuous-bridge \
--serialize-image-pulls=false \
--pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

配置 controller manager 自动批准相关 CSR 请求,如果不配置 –feature-gates=RotateKubeletServerCertificate=true 参数,则即使配置了相关的 RBAC 规则,也只会自动批准 kubelet client 的 renew 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 \
--service-cluster-ip-range=10.254.0.0/16 \
--cluster-name=kubernetes \
--cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem \
--cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \
--service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \
--feature-gates=RotateKubeletServerCertificate=true \
--root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem \
--leader-elect=true \
--experimental-cluster-signing-duration 10m0s \
--node-monitor-grace-period=40s \
--node-monitor-period=5s \
--pod-eviction-timeout=5m0s"

创建自动批准相关 CSR 请求的 ClusterRole,相对于 1.7 版本,1.8 的 apiserver 自动创建了前两条 ClusterRole,所以只需要创建一条就行了

1
2
3
4
5
6
7
8
9
10
# A ClusterRole which instructs the CSR approver to approve a node requesting a
# serving cert matching its client cert.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeserver
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeserver"]
verbs: ["create"]

将 ClusterRole 绑定到适当的用户组,以完成自动批准相关 CSR 请求

1
2
3
4
5
6
7
8
# 自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient --group=system:bootstrappers

# 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient --group=system:nodes

# 自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求
kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeserver --group=system:nodes

一切就绪后启动 kubelet 组件即可,1.8 版本 kubelet 会自动重载证书,以下为 1.8 版本在运行一段时间后的相关证书截图