2019年10月

开源世界中的每个主要发行版都在演进,逐渐将 nftables 作为了默认防火墙。换言之,古老的 iptables 现在已经消亡。本文是有关如何构建 nftables 的教程。

当前,有一个与 nftables 兼容的 iptables-nft 后端,但是很快,即使是它也不再提供了。另外,正如 Red Hat 开发人员所指出的那样,有时它可能会错误地转换规则。我们需要知道如何构建自己的 nftables,而不是依赖于 iptables 到 nftables 的转换器。

在 nftables 中,所有地址族都遵循一个规则。与 iptables 不同,nftables 在用户空间中运行,iptables 中的每个模块都运行在内核(空间)中。它很少需要更新内核,并带有一些新功能,例如映射、地址族和字典。

地址族

地址族确定要处理的数据包的类型。在 nftables 中有六个地址族,它们是:

  • ip
  • ipv6
  • inet
  • arp
  • bridge
  • netdev

在 nftables 中,ipv4 和 ipv6 协议可以被合并为一个称为 inet 的单一地址族。因此,我们不需要指定两个规则:一个用于 ipv4,另一个用于 ipv6。如果未指定地址族,它将默认为 ip 协议,即 ipv4。我们感兴趣的领域是 inet 地址族,因为大多数家庭用户将使用 ipv4 或 ipv6 协议。

nftables

典型的 nftables 规则包含三个部分:表、链和规则。

表是链和规则的容器。它们由其地址族和名称来标识。链包含 inet/arp/bridge/netdev 等协议所需的规则,并具有三种类型:过滤器、NAT 和路由。nftables 规则可以从脚本加载,也可以在终端键入,然后另存为规则集。

对于家庭用户,默认链为过滤器。inet 系列包含以下钩子:

  • Input
  • Output
  • Forward
  • Pre-routing
  • Post-routing

使用脚本还是不用?

最大的问题之一是我们是否可以使用防火墙脚本。答案是:这是你自己的选择。这里有一些建议:如果防火墙中有数百条规则,那么最好使用脚本,但是如果你是典型的家庭用户,则可以在终端中键入命令,然后(保存并在重启时)加载规则集。每种选择都有其自身的优缺点。在本文中,我们将在终端中键入它们以构建防火墙。

nftables 使用一个名为 nft 的程序来添加、创建、列出、删除和加载规则。确保使用以下命令将 nftables 与 conntrackd 和 netfilter-persistent 软件包一起安装,并删除 iptables:

apt-get install nftables conntrackd netfilter-persistent
apt-get purge iptables

nft 需要以 root 身份运行或使用 sudo 运行。使用以下命令分别列出、刷新、删除规则集和加载脚本。

nft list ruleset
nft flush ruleset
nft delete table inet filter
/usr/sbin/nft -f /etc/nftables.conf

输入策略

就像 iptables 一样,防火墙将包含三部分:输入(input)、转发(forward)和输出(output)。在终端中,为输入(input)策略键入以下命令。在开始之前,请确保已刷新规则集。我们的默认策略将会删除所有内容。我们将在防火墙中使用 inet 地址族。将以下规则以 root 身份添加或使用 sudo 运行:

nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0 \; counter \; policy drop \; }

你会注意到有一个名为 priority 0 的东西。这意味着赋予该规则更高的优先级。挂钩通常赋予负整数,这意味着更高的优先级。每个挂钩都有自己的优先级,过滤器链的优先级为 0。你可以检查 nftables Wiki 页面以查看每个挂钩的优先级。

要了解你计算机中的网络接口,请运行以下命令:

ip link show

它将显示已安装的网络接口,一个是本地主机、另一个是以太网端口或无线端口。以太网端口的名称如下所示:enpXsY,其中 XY 是数字,无线端口也是如此。我们必须允许本地主机的流量,并且仅允许从互联网建立的传入连接。

nftables 具有一项称为裁决语句的功能,用于解析规则。裁决语句为 acceptdropqueuejumpgotocontinuereturn。由于这是一个很简单的防火墙,因此我们将使用 acceptdrop 处理数据包。

nft add rule inet filter input iifname lo accept
nft add rule inet filter input iifname enpXsY ct state new, established, related accept

接下来,我们必须添加规则以保护我们免受隐秘扫描。并非所有的隐秘扫描都是恶意的,但大多数都是。我们必须保护网络免受此类扫描。第一组规则列出了要测试的 TCP 标志。在这些标志中,第二组列出了要与第一组匹配的标志。

nft add rule inet filter input iifname enpXsY tcp flags \& \(syn\|fin\) == \(syn\|fin\) drop
nft add rule inet filter input iifname enpXsY tcp flags \& \(syn\|rst\) == \(syn\|rst\) drop
nft add rule inet filter input iifname enpXsY tcp flags \& \(fin\|rst\) == \(fin\|rst\) drop
nft add rule inet filter input iifname enpXsY tcp flags \& \(ack\|fin\) == fin drop
nft add rule inet filter input iifname enpXsY tcp flags \& \(ack\|psh\) == psh drop
nft add rule inet filter input iifname enpXsY tcp flags \& \(ack\|urg\) == urg drop

记住,我们在终端中键入这些命令。因此,我们必须在一些特殊字符之前添加一个反斜杠,以确保终端能够正确解释该斜杠。如果你使用的是脚本,则不需要这样做。

关于 ICMP 的警告

互联网控制消息协议(ICMP)是一种诊断工具,因此不应完全丢弃该流量。完全阻止 ICMP 的任何尝试都是不明智的,因为它还会导致停止向我们提供错误消息。仅启用最重要的控制消息,例如回声请求、回声应答、目的地不可达和超时等消息,并拒绝其余消息。回声请求和回声应答是 ping 的一部分。在输入策略中,我们仅允许回声应答、而在输出策略中,我们仅允许回声请求。

nft add rule inet filter input iifname enpXsY icmp type { echo-reply, destination-unreachable, time-exceeded } limit rate 1/second accept
nft add rule inet filter input iifname enpXsY ip protocol icmp drop

最后,我们记录并丢弃所有无效数据包。

nft add rule inet filter input iifname enpXsY ct state invalid log flags all level info prefix \”Invalid-Input: \”
nft add rule inet filter input iifname enpXsY ct state invalid drop

转发和输出策略

在转发和输出策略中,默认情况下我们将丢弃数据包,仅接受已建立连接的数据包。

nft add chain inet filter forward { type filter hook forward priority 0 \; counter \; policy drop \; }
nft add rule inet filter forward ct state established, related accept
nft add rule inet filter forward ct state invalid drop
nft add chain inet filter output { type filter hook output priority 0 \; counter \; policy drop \; }

典型的桌面用户只需要端口 80 和 443 即可访问互联网。最后,允许可接受的 ICMP 协议并在记录无效数据包时丢弃它们。

nft add rule inet filter output oifname enpXsY tcp dport { 80, 443 } ct state established accept
nft add rule inet filter output oifname enpXsY icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 1/second accept
nft add rule inet filter output oifname enpXsY ip protocol icmp drop
nft add rule inet filter output oifname enpXsY ct state invalid log flags all level info prefix \”Invalid-Output: \”
nft add rule inet filter output oifname enpXsY ct state invalid drop

现在我们必须保存我们的规则集,否则重新启动时它将丢失。为此,请运行以下命令:

sudo nft list ruleset. > /etc/nftables.conf

我们须在引导时加载 nftables,以下将在 systemd 中启用 nftables 服务:

sudo systemctl enable nftables

接下来,编辑 nftables 单元文件以删除 Execstop 选项,以避免在每次引导时刷新规则集。该文件通常位于 /etc/systemd/system/sysinit.target.wants/nftables.service。现在重新启动nftables:

sudo systemctl restart nftables

在 rsyslog 中记录日志

当你记录丢弃的数据包时,它们直接进入 syslog,这使得读取该日志文件非常困难。最好将防火墙日志重定向到单独的文件。在 /var/log 目录中创建一个名为 nftables 的目录,并在其中创建两个名为 input.logoutput.log 的文件,分别存储输入和输出日志。确保系统中已安装 rsyslog。现在转到 /etc/rsyslog.d 并创建一个名为 nftables.conf 的文件,其内容如下:

:msg,regex,”Invalid-Input: “ -/var/log/nftables/Input.log
:msg,regex,”Invalid-Output: “ -/var/log/nftables/Output.log & stop

现在,我们必须确保日志是可管理的。为此,使用以下代码在 /etc/logrotate.d 中创建另一个名为 nftables 的文件:

/var/log/nftables/* { rotate 5 daily maxsize 50M missingok notifempty delaycompress compress postrotate invoke-rc.d rsyslog rotate > /dev/null endscript }

重新启动 nftables。现在,你可以检查你的规则集。如果你觉得在终端中键入每个命令很麻烦,则可以使用脚本来加载 nftables 防火墙。我希望本文对保护你的系统有用。


via: https://opensourceforu.com/2019/10/transition-to-nftables/

作者:Vijay Marcel D 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

上一篇:模式

在这篇帖子中,我们将会通过为应用添加社交登录功能进入后端开发。

社交登录的工作方式十分简单:用户点击链接,然后重定向到 GitHub 授权页面。当用户授予我们对他的个人信息的访问权限之后,就会重定向回登录页面。下一次尝试登录时,系统将不会再次请求授权,也就是说,我们的应用已经记住了这个用户。这使得整个登录流程看起来就和你用鼠标单击一样快。

如果进一步考虑其内部实现的话,过程就会变得复杂起来。首先,我们需要注册一个新的 GitHub OAuth 应用

这一步中,比较重要的是回调 URL。我们将它设置为 http://localhost:3000/api/oauth/github/callback。这是因为,在开发过程中,我们总是在本地主机上工作。一旦你要将应用交付生产,请使用正确的回调 URL 注册一个新的应用。

注册以后,你将会收到“客户端 id”和“安全密钥”。安全起见,请不要与任何人分享他们 ?

顺便让我们开始写一些代码吧。现在,创建一个 main.go 文件:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "os"
    "strconv"

    "github.com/gorilla/securecookie"
    "github.com/joho/godotenv"
    "github.com/knq/jwt"
    _ "github.com/lib/pq"
    "github.com/matryer/way"
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/github"
)

var origin *url.URL
var db *sql.DB
var githubOAuthConfig *oauth2.Config
var cookieSigner *securecookie.SecureCookie
var jwtSigner jwt.Signer

func main() {
    godotenv.Load()

    port := intEnv("PORT", 3000)
    originString := env("ORIGIN", fmt.Sprintf("http://localhost:%d/", port))
    databaseURL := env("DATABASE_URL", "postgresql://[email protected]:26257/messenger?sslmode=disable")
    githubClientID := os.Getenv("GITHUB_CLIENT_ID")
    githubClientSecret := os.Getenv("GITHUB_CLIENT_SECRET")
    hashKey := env("HASH_KEY", "secret")
    jwtKey := env("JWT_KEY", "secret")

    var err error
    if origin, err = url.Parse(originString); err != nil || !origin.IsAbs() {
        log.Fatal("invalid origin")
        return
    }

    if i, err := strconv.Atoi(origin.Port()); err == nil {
        port = i
    }

    if githubClientID == "" || githubClientSecret == "" {
        log.Fatalf("remember to set both $GITHUB_CLIENT_ID and $GITHUB_CLIENT_SECRET")
        return
    }

    if db, err = sql.Open("postgres", databaseURL); err != nil {
        log.Fatalf("could not open database connection: %v\n", err)
        return
    }
    defer db.Close()
    if err = db.Ping(); err != nil {
        log.Fatalf("could not ping to db: %v\n", err)
        return
    }

    githubRedirectURL := *origin
    githubRedirectURL.Path = "/api/oauth/github/callback"
    githubOAuthConfig = &oauth2.Config{
        ClientID:     githubClientID,
        ClientSecret: githubClientSecret,
        Endpoint:     github.Endpoint,
        RedirectURL:  githubRedirectURL.String(),
        Scopes:       []string{"read:user"},
    }

    cookieSigner = securecookie.New([]byte(hashKey), nil).MaxAge(0)

    jwtSigner, err = jwt.HS256.New([]byte(jwtKey))
    if err != nil {
        log.Fatalf("could not create JWT signer: %v\n", err)
        return
    }

    router := way.NewRouter()
    router.HandleFunc("GET", "/api/oauth/github", githubOAuthStart)
    router.HandleFunc("GET", "/api/oauth/github/callback", githubOAuthCallback)
    router.HandleFunc("GET", "/api/auth_user", guard(getAuthUser))

    log.Printf("accepting connections on port %d\n", port)
    log.Printf("starting server at %s\n", origin.String())
    addr := fmt.Sprintf(":%d", port)
    if err = http.ListenAndServe(addr, router); err != nil {
        log.Fatalf("could not start server: %v\n", err)
    }
}

func env(key, fallbackValue string) string {
    v, ok := os.LookupEnv(key)
    if !ok {
        return fallbackValue
    }
    return v
}

func intEnv(key string, fallbackValue int) int {
    v, ok := os.LookupEnv(key)
    if !ok {
        return fallbackValue
    }
    i, err := strconv.Atoi(v)
    if err != nil {
        return fallbackValue
    }
    return i
}

安装依赖项:

go get -u github.com/gorilla/securecookie
go get -u github.com/joho/godotenv
go get -u github.com/knq/jwt
go get -u github.com/lib/pq
ge get -u github.com/matoous/go-nanoid
go get -u github.com/matryer/way
go get -u golang.org/x/oauth2

我们将会使用 .env 文件来保存密钥和其他配置。请创建这个文件,并保证里面至少包含以下内容:

GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret

我们还要用到的其他环境变量有:

  • PORT:服务器运行的端口,默认值是 3000
  • ORIGIN:你的域名,默认值是 http://localhost:3000/。我们也可以在这里指定端口。
  • DATABASE_URL:Cockroach 数据库的地址。默认值是 postgresql://[email protected]:26257/messenger?sslmode=disable
  • HASH_KEY:用于为 cookie 签名的密钥。没错,我们会使用已签名的 cookie 来确保安全。
  • JWT_KEY:用于签署 JSON 网络令牌 Web Token 的密钥。

因为代码中已经设定了默认值,所以你也不用把它们写到 .env 文件中。

在读取配置并连接到数据库之后,我们会创建一个 OAuth 配置。我们会使用 ORIGIN 信息来构建回调 URL(就和我们在 GitHub 页面上注册的一样)。我们的数据范围设置为 “read:user”。这会允许我们读取公开的用户信息,这里我们只需要他的用户名和头像就够了。然后我们会初始化 cookie 和 JWT 签名器。定义一些端点并启动服务器。

在实现 HTTP 处理程序之前,让我们编写一些函数来发送 HTTP 响应。

func respond(w http.ResponseWriter, v interface{}, statusCode int) {
    b, err := json.Marshal(v)
    if err != nil {
        respondError(w, fmt.Errorf("could not marshal response: %v", err))
        return
    }
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(statusCode)
    w.Write(b)
}

func respondError(w http.ResponseWriter, err error) {
    log.Println(err)
    http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

第一个函数用来发送 JSON,而第二个将错误记录到控制台并返回一个 500 Internal Server Error 错误信息。

OAuth 开始

所以,用户点击写着 “Access with GitHub” 的链接。该链接指向 /api/oauth/github,这将会把用户重定向到 github。

func githubOAuthStart(w http.ResponseWriter, r *http.Request) {
    state, err := gonanoid.Nanoid()
    if err != nil {
        respondError(w, fmt.Errorf("could not generte state: %v", err))
        return
    }

    stateCookieValue, err := cookieSigner.Encode("state", state)
    if err != nil {
        respondError(w, fmt.Errorf("could not encode state cookie: %v", err))
        return
    }

    http.SetCookie(w, &http.Cookie{
        Name:     "state",
        Value:    stateCookieValue,
        Path:     "/api/oauth/github",
        HttpOnly: true,
    })
    http.Redirect(w, r, githubOAuthConfig.AuthCodeURL(state), http.StatusTemporaryRedirect)
}

OAuth2 使用一种机制来防止 CSRF 攻击,因此它需要一个“状态”(state)。我们使用 Nanoid() 来创建一个随机字符串,并用这个字符串作为状态。我们也把它保存为一个 cookie。

OAuth 回调

一旦用户授权我们访问他的个人信息,他将会被重定向到这个端点。这个 URL 的查询字符串上将会包含状态(state)和授权码(code): /api/oauth/github/callback?state=&code=

const jwtLifetime = time.Hour * 24 * 14

type GithubUser struct {
    ID        int     `json:"id"`
    Login     string  `json:"login"`
    AvatarURL *string `json:"avatar_url,omitempty"`
}

type User struct {
    ID        string  `json:"id"`
    Username  string  `json:"username"`
    AvatarURL *string `json:"avatarUrl"`
}

func githubOAuthCallback(w http.ResponseWriter, r *http.Request) {
    stateCookie, err := r.Cookie("state")
    if err != nil {
        http.Error(w, http.StatusText(http.StatusTeapot), http.StatusTeapot)
        return
    }

    http.SetCookie(w, &http.Cookie{
        Name:     "state",
        Value:    "",
        MaxAge:   -1,
        HttpOnly: true,
    })

    var state string
    if err = cookieSigner.Decode("state", stateCookie.Value, &state); err != nil {
        http.Error(w, http.StatusText(http.StatusTeapot), http.StatusTeapot)
        return
    }

    q := r.URL.Query()

    if state != q.Get("state") {
        http.Error(w, http.StatusText(http.StatusTeapot), http.StatusTeapot)
        return
    }

    ctx := r.Context()

    t, err := githubOAuthConfig.Exchange(ctx, q.Get("code"))
    if err != nil {
        respondError(w, fmt.Errorf("could not fetch github token: %v", err))
        return
    }

    client := githubOAuthConfig.Client(ctx, t)
    resp, err := client.Get("https://api.github.com/user")
    if err != nil {
        respondError(w, fmt.Errorf("could not fetch github user: %v", err))
        return
    }

    var githubUser GithubUser
    if err = json.NewDecoder(resp.Body).Decode(&githubUser); err != nil {
        respondError(w, fmt.Errorf("could not decode github user: %v", err))
        return
    }
    defer resp.Body.Close()

    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        respondError(w, fmt.Errorf("could not begin tx: %v", err))
        return
    }

    var user User
    if err = tx.QueryRowContext(ctx, `
        SELECT id, username, avatar_url FROM users WHERE github_id = $1
    `, githubUser.ID).Scan(&user.ID, &user.Username, &user.AvatarURL); err == sql.ErrNoRows {
        if err = tx.QueryRowContext(ctx, `
            INSERT INTO users (username, avatar_url, github_id) VALUES ($1, $2, $3)
            RETURNING id
        `, githubUser.Login, githubUser.AvatarURL, githubUser.ID).Scan(&user.ID); err != nil {
            respondError(w, fmt.Errorf("could not insert user: %v", err))
            return
        }
        user.Username = githubUser.Login
        user.AvatarURL = githubUser.AvatarURL
    } else if err != nil {
        respondError(w, fmt.Errorf("could not query user by github ID: %v", err))
        return
    }

    if err = tx.Commit(); err != nil {
        respondError(w, fmt.Errorf("could not commit to finish github oauth: %v", err))
        return
    }

    exp := time.Now().Add(jwtLifetime)
    token, err := jwtSigner.Encode(jwt.Claims{
        Subject:    user.ID,
        Expiration: json.Number(strconv.FormatInt(exp.Unix(), 10)),
    })
    if err != nil {
        respondError(w, fmt.Errorf("could not create token: %v", err))
        return
    }

    expiresAt, _ := exp.MarshalText()

    data := make(url.Values)
    data.Set("token", string(token))
    data.Set("expires_at", string(expiresAt))

    http.Redirect(w, r, "/callback?"+data.Encode(), http.StatusTemporaryRedirect)
}

首先,我们会尝试使用之前保存的状态对 cookie 进行解码。并将其与查询字符串中的状态进行比较。如果它们不匹配,我们会返回一个 418 I'm teapot(未知来源)错误。

接着,我们使用授权码生成一个令牌。这个令牌被用于创建 HTTP 客户端来向 GitHub API 发出请求。所以最终我们会向 https://api.github.com/user 发送一个 GET 请求。这个端点将会以 JSON 格式向我们提供当前经过身份验证的用户信息。我们将会解码这些内容,一并获取用户的 ID、登录名(用户名)和头像 URL。

然后我们将会尝试在数据库上找到具有该 GitHub ID 的用户。如果没有找到,就使用该数据创建一个新的。

之后,对于新创建的用户,我们会发出一个将用户 ID 作为主题(Subject)的 JSON 网络令牌,并使用该令牌重定向到前端,查询字符串中一并包含该令牌的到期日(Expiration)。

这一 Web 应用也会被用在其他帖子,但是重定向的链接会是 /callback?token=&expires_at=。在那里,我们将会利用 JavaScript 从 URL 中获取令牌和到期日,并通过 Authorization 标头中的令牌以 Bearer token_here 的形式对 /api/auth_user 进行 GET 请求,来获取已认证的身份用户并将其保存到 localStorage。

Guard 中间件

为了获取当前已经过身份验证的用户,我们设计了 Guard 中间件。这是因为在接下来的文章中,我们会有很多需要进行身份认证的端点,而中间件将会允许我们共享这一功能。

type ContextKey struct {
    Name string
}

var keyAuthUserID = ContextKey{"auth_user_id"}

func guard(handler http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var token string
        if a := r.Header.Get("Authorization"); strings.HasPrefix(a, "Bearer ") {
            token = a[7:]
        } else if t := r.URL.Query().Get("token"); t != "" {
            token = t
        } else {
            http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
            return
        }

        var claims jwt.Claims
        if err := jwtSigner.Decode([]byte(token), &claims); err != nil {
            http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
            return
        }

        ctx := r.Context()
        ctx = context.WithValue(ctx, keyAuthUserID, claims.Subject)

        handler(w, r.WithContext(ctx))
    }
}

首先,我们尝试从 Authorization 标头或者是 URL 查询字符串中的 token 字段中读取令牌。如果没有找到,我们需要返回 401 Unauthorized(未授权)错误。然后我们将会对令牌中的申明进行解码,并使用该主题作为当前已经过身份验证的用户 ID。

现在,我们可以用这一中间件来封装任何需要授权的 http.handlerFunc,并且在处理函数的上下文中保有已经过身份验证的用户 ID。

var guarded = guard(func(w http.ResponseWriter, r *http.Request) {
    authUserID := r.Context().Value(keyAuthUserID).(string)
})

获取认证用户

func getAuthUser(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    authUserID := ctx.Value(keyAuthUserID).(string)

    var user User
    if err := db.QueryRowContext(ctx, `
        SELECT username, avatar_url FROM users WHERE id = $1
    `, authUserID).Scan(&user.Username, &user.AvatarURL); err == sql.ErrNoRows {
        http.Error(w, http.StatusText(http.StatusTeapot), http.StatusTeapot)
        return
    } else if err != nil {
        respondError(w, fmt.Errorf("could not query auth user: %v", err))
        return
    }

    user.ID = authUserID

    respond(w, user, http.StatusOK)
}

我们使用 Guard 中间件来获取当前经过身份认证的用户 ID 并查询数据库。

这一部分涵盖了后端的 OAuth 流程。在下一篇帖子中,我们将会看到如何开始与其他用户的对话。


via: https://nicolasparada.netlify.com/posts/go-messenger-oauth/

作者:Nicolás Parada 选题:lujun9972 译者:PsiACE 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

MX Linux 19

MX Linux 18 是我在最佳 Linux 发行版中的主要推荐的发行版之一,特别是当你在考虑 Ubuntu 以外的发行版时。

它基于 Debian 9.6 “Stretch”,具有令人难以置信的快速流畅的体验。

现在,作为该发行版的主要升级版本,MX Linux 19 带来了许多重大改进和变更。在这里,我们将看一下主要亮点。

MX Linux 19 中的新功能

Debian 10 “Buster”

这个值得一提,因为 Debian 10 实际上是 MX Linux 18 所基于的 Debian 9.6 “Stretch” 的主要升级。

如果你对 Debian 10 “Buster” 的变化感到好奇,建议你阅读有关 Debian 10 “Buster” 的新功能的文章。

Xfce 桌面 4.14

Xfce 4.14 正是 Xfce 开发团队提供的最新产品。就个人而言,我不是 Xfce 桌面环境的粉丝,但是当你在 Linux 发行版(尤其是 MX Linux 19)上使用它时,它超快的性能会让你惊叹。

或许你会感兴趣,我们也有一个快速指南来帮助你自定义 Xfce

升级的软件包及最新的 Debian 内核 4.19

除了 GIMP、MESA、Firefox 等的更新软件包之外,它还随附有 Debian “Buster” 可用的最新内核 4.19。

升级的 MX 系列应用

如果你以前使用过 MX Linux,则可能会知道它已经预装了有用的 MX 系列应用,可以帮助你快速完成更多工作。

像 MX-installer 和 MX-packageinstaller 这样的应用程序得到了显著改进。

除了这两个以外,所有其他 MX 工具也已不同程度的进行了更新和修复错误、添加了新的翻译(或只是改善了用户体验)。

其它改进

考虑到这是一次重大升级,很明显,底层的更改要多于表面(包括最新的 antiX live 系统更新)。

你可以在他们的官方公告中查看更多详细信息。你还可以从开发人员那里观看以下视频,它介绍了 MX Linux 19 中的所有新功能:

获取 MX Linux 19

即使是你现在正在使用 MX Linux 18 版本,你也无法升级到 MX Linux 19。你需要像其他人一样进行全新安装。

你可以从此页面下载 MX Linux 19:

结语

在 MX Linux 18 上,我在使用 WiFi 适配器时遇到了问题,通过论坛解决了该问题,但看来 MX Linux 19 仍未解决该问题。因此,如果你在安装 MX Linux 19 之后遇到了相同的问题,你可能想要查看一下我的论坛帖子

如果你使用的是 MX Linux 18,那么这绝对是一个令人印象深刻的升级。

你尝试过了吗?你对新的 MX Linux 19 版本有何想法?让我知道你在以下评论中的想法。


via: https://itsfoss.com/mx-linux-19/

作者:Ankush Das 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

从业计算机行业以来,我接触过很多计算机硬件厂商,不知自何时开始,从我们的身边的个人电脑到企业级的服务器和存储设备,越来越多的见到了戴尔的身影。就在我前两天参加的戴尔科技峰会的一个媒体群访会上,戴尔科技集团全球副总裁、大中华区数据中心销售总经理孔大勇先生就笑言,“在座的诸位中有 15 台笔记本电脑,其中 9 台是戴尔的”。更别说如浩如烟海般充斥在数据中心的各式戴尔设备。

可能在很多人眼里,戴尔还是那个生产、销售硬件服务器的戴尔,但是很多人不知道的是,戴尔已经在云计算领域布局多年。

云计算这个 IT 时髦词虽然已经提出很久了,但是令人有些意外的是,这个时髦词居然历久弥新,在 IT 领域这么变化日新月异的行业中,还能不断焕发新的变化。从早期的分布式计算、虚拟技术,到现在的容器技术,从公有云、私有云,到混合云、多云,不经意间,一篇篇新的技术篇章掀过,我们所面临的已经是一个崭新的云时代。

这次有幸参加戴尔科技峰会 2019,让我恍若置身一场云原生大会,除了很多的新技术突破和硬件新品发布,戴尔也在云计算领域取得了长足的发展。由于我们长期以来一直关注云技术的发展,所以我专门着眼于观察戴尔在云计算方面的新变化,下面就让我来给大家介绍一下本次大会中我的感受。

戴尔的云端之路

自 2010 年戴尔发布 高效企业生态系统 Efficient Enterprise Ecosystem (E3)的云计算战略以后,就开始不断布局云计算生态,将戴尔从一个传统的计算机硬件企业,转变成为一个软硬件两把抓的云计算强企。

得益于多年企业级硬件的产品与设备,戴尔从一开始就为其在企业级场景下的硬件设施打好了基础,而近年来,戴尔也不断并购了一些企业,并以此为契机,增加了自己在云计算领域的能力。

近年来,戴尔收购了 EMC,从而掌握了虚拟化领域巨头 VMWare,又在其后收购了Pivotal (后者是顶尖的云计算平台公司,我们所熟悉的 12306 的架构中,就有一大部分在使用 Pivotal 的产品),不断增强自己在云计算领域的能力,并借助于自己在传统硬件领域的强项,去改变企业、政府对于云计算的认识,协助他们完成数字化转型。同时,可能会令很多人意外的是,戴尔也是 Kubernetes 项目代码贡献度全球第二的企业,拥有丰富的 Kubernetes 开发人才。

和我们所熟悉的很多公有云厂商不同,他们很多是从开始做云的时候,就决定要做公有云,希望去服务尽可能多的用户,服务更多的中长尾用户。戴尔则是更关注于企业级市场,关注企业用户的需求。当然,这也是源自于戴尔在服务器等企业级市场的深耕,能够精确把握企业对于云计算的需求。

源自不同认识的混合云

在不断服务企业级客户的过程中,戴尔收集到了大量的用户反馈,企业级用户往往不同于个人用户,将自己的所有业务都集中在一个平台上,企业级客户会同时使用两家甚至是更多的云平台,以确保自己的业务不会因为某一家的云服务出现问题而整体业务崩盘。单一平台所构建的信息孤岛对于企业级用户来说,是一个很大的风险,这也是为什么近些年来,我们看到 Kubernetes 的不断发展。企业面临着信息孤岛的问题而不知如何解决,Kubernetes 是一个标准化的云计算解决方案,帮助他们提升产品和能力的可迁移性,而戴尔则对该问题提供了更进一步的解决方案。

前面我们提到,VMWare 目前是属于戴尔公司的,而 VMWare 本身,是目前全球用量最大的云计算虚拟化平台,你所知道或不知道的云计算平台,他们的背后运转的很多都是 VMWare 计算平台。得益于 VMWare 多年的积累,戴尔拥有丰富的私有云建设经验,可以在企业现有的基础设施上快速构建起一套能力强大的私有云体系。同时,因为不少云计算平台使用的也是 VMWare,使得企业自行构建的私有云能够更加轻松的与使用了 VMWare 的公有云结合,构建出一体化体验的云计算平台,让用户可以在一个平台上调度和管理多个不同的云计算平台,在这个平台上做计算、做数据挖掘、做资源调度,帮助企业更加简单的构建私有云。

这样构建出的私有云,拥有真正意义上的“混合”云的能力,而不再是仅仅与某一家云计算厂商提供的能力所结合,它可以让你同时和多个不同的云计算平台对接,还可以和你自己企业内部构建的私有云所结合,让不同云计算平台的业务可以通过戴尔所提供的服务和产品无缝对接,让业务正常运转。

VCF 如何解决问题

由我们所熟悉的一些云计算公司,诸如 AWS、Azure、Google ,以及全球 4000 多家 VCPP(VMware Cloud Provider Program)项目成员所组成的VCF(VMware Cloud Foundation)建立起一套云计算基础设施的标准,都采用统一的 VCF 作为其标准,构建自己的云计算服务。而基于VMware最佳实践来搭建的软件定义的数据中心,其中所有的云都是基于同一个标准、同一个架构,其中的产品也就可以随意的在不同的云计算平台中任意迁移。

对于企业来说,VCF 的存在大大降低了其产品在不同服务商之间切换的成本,企业可以根据自己的需求,随意的在其所使用的几个云服务商之间进行切换,甚至是切换到私有云上!相比于我们熟悉的 Kubernetes,VCF 在易用性上的投入,更是让企业可以简单的实现业务需求,支撑业务的整体运转。

SDDC 的未来

SDDC 是 VCF 的一个产品组件,想来这个词去描述戴尔的混合云战略真是再准确不过了。

什么是 SDDC?和我们所熟悉的 SDN( 软件定义网络 Software-defined Network )类似,SDDC 就是 软件定义的数据中心 Software-Defined DataCenter ,以软件的方式来定义数据中心。而能够做到这些,源自于戴尔自己多年制作企业级服务器和其所收购的 VMWare 多年来在虚拟化领域的投入。VMware 目前是全球私有云环境里面市场占有率体量也是最大的。和其他云计算厂商相比,戴尔拥有业界体量最大、份额最高、方案最全、价值最高的方案,可以满足用户的不同场景下的需求。

在戴尔看来,未来企业级云计算的市场会逐渐归一,所有的公有云也好、私有云也好,终将会回归到混合云的模式,因为企业不可能将所有的核心数据都交付给云计算厂商,他们需要一个私有云,帮助承载和处理企业内部的一些机密数据。而私有云和公有云的对接,就会逐渐成为一个大的问题。做虚拟化出身的 VMWare 帮助戴尔解决了自己的问题,提出了 SDDC 的方式,来完成这个业务需求,并基于 VMWare 多年的积累,为 SDDC 加入大量云计算企业的支持,为客户拉了一针强心剂。

在中国,为中国

在过去的数年里,戴尔不但在技术研究方面深入到云计算行业,也将自己的云计算产品推向市场,接受市场的考验。

比如说,戴尔近几年借助于区域服务商,将其云计算的产品铺设到政府和企业场景中,帮助他们完成信息化、数字化。

很多企业和部门内部的基础设施虽然投入昂贵,但是,由于维护和管理不善,从而导致大量的病毒、木马潜伏在服务器内,占用了设备性能,并造成了数据泄露和安全隐患。此外,也因为基础设施维护的不完善,没有办法有效利用基础设施资源,更谈不上数据的共享。同时导致了各部门都需要自行建设基础设施,造成了大量的基础设施和资源的浪费,数据中心建设标准参差不齐,无法保证数据的安全和业务的稳定,为服务埋下了隐患。

引入戴尔的混合云方案以后,机构对机房进行统一,建设一个主数据中心,从而完成管理集群、高性能集群、vSAN 集群等,帮助他们构建了一套性能良好、基础设施完备的内部云方案,同时,得益于 VMWare 本身基础设施建设的能力,还引入了 vSAN,借助其拓展能力,实现了双活的副数据中心的构建,以确保数据的安全和业务的运转。

借助不断推动企业、政府的数字化、信息化,戴尔实现了自己在几年前定下的诺言,“在中国、为中国”,帮助中国变得越来越好。

结语

这一次的戴尔科技峰会让我大开眼界,过去那个只做硬件的戴尔,如今不仅做了云,还做取得了长足的发展,站在了云计算技术的前沿,不知道未来,戴尔还能够为我们带来什么更加亮眼的产品?

 title=

IoT (物联网)设备正在迅速改变我们的生活。这些设备无处不在,从我们的家庭到其它行业。根据一些预测数据,到 2020 年,将会有 100 亿个 IoT 设备。到 2025 年,该数量将增长到 220 亿。目前,物联网已经在很多领域得到了应用,包括智能家居、工业生产过程、农业甚至医疗保健领域。伴随着如此广泛的应用,物联网显然已经成为近年来的热门话题之一。

多种因素促成了物联网设备在多个学科的爆炸式增长。这其中包括低成本处理器和无线连接的的可用性,以及开源平台的信息交流推动了物联网领域的创新。与传统的应用程序开发相比,物联网设备的开发成指数级增长,因为它的资源是开源的。

在解释如何使用物联网设备来保护儿童之前,必须对物联网技术有基本的了解。

IoT 设备是什么?

IoT 设备是指那些在没有人类参与的情况下彼此之间可以通信的设备。因此,许多专家并不将智能手机和计算机视为物联网设备。此外,物联网设备必须能够收集数据并且能将收集到的数据传送到其他设备或云端进行处理。

然而,在某些领域中,我们需要探索物联网的潜力。儿童往往是脆弱的,他们很容易成为犯罪分子和其他蓄意伤害者的目标。无论在物理世界还是数字世界中,儿童都很容易面临犯罪的威胁。因为父母不能始终亲自到场保护孩子;这就是为什么需要监视工具了。

除了适用于儿童的可穿戴设备外,还有许多父母监视应用程序,例如 Xnspy,可实时监控儿童并提供信息的实时更新。这些工具可确保儿童安全。可穿戴设备确保儿童身体上的安全性,而家长监控应用可确保儿童的上网安全。

由于越来越多的孩子花费时间在智能手机上,毫无意外地,他们也就成为诈骗分子的主要目标。此外,由于恋童癖、网络自夸和其他犯罪在网络上的盛行,儿童也有可能成为网络欺凌的目标。

这些解决方案够吗?我们需要找到物联网解决方案,以确保孩子们在网上和线下的安全。在当代,我们如何确保孩子的安全?我们需要提出创新的解决方案。 物联网可以帮助保护孩子在学校和家里的安全。

物联网的潜力

物联网设备提供的好处很多。举例来说,父母可以远程监控自己的孩子,而又不会显得太霸道。因此,儿童在拥有安全环境的同时也会有空间和自由让自己变得独立。

而且,父母也不必在为孩子的安全而担忧。物联网设备可以提供 7x24 小时的信息更新。像 Xnspy 之类的监视应用程序在提供有关孩子的智能手机活动信息方面更进了一步。随着物联网设备变得越来越复杂,拥有更长使用寿命的电池只是一个时间问题。诸如位置跟踪器之类的物联网设备可以提供有关孩子下落的准确详细信息,所以父母不必担心。

虽然可穿戴设备已经非常好了,但在确保儿童安全方面,这些通常还远远不够。因此,要为儿童提供安全的环境,我们还需要其他方法。许多事件表明,儿童在学校比其他任何公共场所都容易受到攻击。因此,学校需要采取安全措施,以确保儿童和教师的安全。在这一点上,物联网设备可用于检测潜在威胁并采取必要的措施来防止攻击。威胁检测系统包括摄像头。系统一旦检测到威胁,便可以通知当局,如一些执法机构和医院。智能锁等设备可用于封锁学校(包括教室),来保护儿童。除此之外,还可以告知父母其孩子的安全,并立即收到有关威胁的警报。这将需要实施无线技术,例如 Wi-Fi 和传感器。因此,学校需要制定专门用于提供教室安全性的预算。

智能家居实现拍手关灯,也可以让你的家庭助手帮你关灯。同样,物联网设备也可用在屋内来保护儿童。在家里,物联网设备(例如摄像头)为父母在照顾孩子时提供 100% 的可见性。当父母不在家里时,可以使用摄像头和其他传感器检测是否发生了可疑活动。其他设备(例如连接到这些传感器的智能锁)可以锁门和窗,以确保孩子们的安全。

同样,可以引入许多物联网解决方案来确保孩子的安全。

有多好就有多坏

物联网设备中的传感器会创建大量数据。数据的安全性是至关重要的一个因素。收集的有关孩子的数据如果落入不法分子手中会存在危险。因此,需要采取预防措施。IoT 设备中泄露的任何数据都可用于确定行为模式。因此,必须对提供不违反用户隐私的安全物联网解决方案投入资金。

IoT 设备通常连接到 Wi-Fi,用于设备之间传输数据。未加密数据的不安全网络会带来某些风险。这样的网络很容易被窃听。黑客可以使用此类网点来入侵系统。他们还可以将恶意软件引入系统,从而使系统变得脆弱、易受攻击。此外,对设备和公共网络(例如学校的网络)的网络攻击可能导致数据泄露和私有数据盗用。 因此,在实施用于保护儿童的物联网解决方案时,保护网络和物联网设备的总体计划必须生效。

物联网设备保护儿童在学校和家里的安全的潜力尚未发现有什么创新。我们需要付出更多努力来保护连接 IoT 设备的网络安全。此外,物联网设备生成的数据可能落入不法分子手中,从而造成更多麻烦。因此,这是物联网安全至关重要的一个领域。


via: https://opensourceforu.com/2019/10/how-to-use-iot-devices-to-keep-children-safe/

作者:Andrew Carroll 选题:lujun9972 译者:Morisun029 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Rsyslog 是一个自由开源的日志记录程序,在 CentOS 8 和 RHEL 8 系统上默认可用。它提供了一种从客户端节点到单个中央服务器的“集中日志”的简单有效的方法。日志集中化有两个好处。首先,它简化了日志查看,因为系统管理员可以在一个中心节点查看远程服务器的所有日志,而无需登录每个客户端系统来检查日志。如果需要监视多台服务器,这将非常有用,其次,如果远程客户端崩溃,你不用担心丢失日志,因为所有日志都将保存在中心的 Rsyslog 服务器上。rsyslog 取代了仅支持 UDP 协议的 syslog。它以优异的功能扩展了基本的 syslog 协议,例如在传输日志时支持 UDP 和 TCP 协议,增强的过滤功能以及灵活的配置选项。让我们来探讨如何在 CentOS 8 / RHEL 8 系统中配置 Rsyslog 服务器。

configure-rsyslog-centos8-rhel8

预先条件

我们将搭建以下实验环境来测试集中式日志记录过程:

  • Rsyslog 服务器 CentOS 8 Minimal IP 地址: 10.128.0.47
  • 客户端系统 RHEL 8 Minimal IP 地址: 10.128.0.48

通过上面的设置,我们将演示如何设置 Rsyslog 服务器,然后配置客户端系统以将日志发送到 Rsyslog 服务器进行监视。

让我们开始!

在 CentOS 8 上配置 Rsyslog 服务器

默认情况下,Rsyslog 已安装在 CentOS 8 / RHEL 8 服务器上。要验证 Rsyslog 的状态,请通过 SSH 登录并运行以下命令:

$ systemctl status rsyslog

示例输出:

rsyslog-service-status-centos8

如果由于某种原因 Rsyslog 不存在,那么可以使用以下命令进行安装:

$ sudo yum install rsyslog

接下来,你需要修改 Rsyslog 配置文件中的一些设置。打开配置文件:

$ sudo vim /etc/rsyslog.conf

滚动并取消注释下面的行,以允许通过 UDP 协议接收日志:

module(load="imudp") # needs to be done just once
input(type="imudp" port="514")

rsyslog-conf-centos8-rhel8

同样,如果你希望启用 TCP rsyslog 接收,请取消注释下面的行:

module(load="imtcp") # needs to be done just once
input(type="imtcp" port="514")

rsyslog-conf-tcp-centos8-rhel8

保存并退出配置文件。

要从客户端系统接收日志,我们需要在防火墙上打开 Rsyslog 默认端口 514。为此,请运行:

# sudo firewall-cmd  --add-port=514/tcp  --zone=public  --permanent

接下来,重新加载防火墙保存更改:

# sudo firewall-cmd --reload

示例输出:

firewall-ports-rsyslog-centos8

接下来,重启 Rsyslog 服务器:

$ sudo systemctl restart rsyslog

要在启动时运行 Rsyslog,运行以下命令:

$ sudo systemctl enable rsyslog

要确认 Rsyslog 服务器正在监听 514 端口,请使用 netstat 命令,如下所示:

$ sudo netstat -pnltu

示例输出:

netstat-rsyslog-port-centos8

完美!我们已经成功配置了 Rsyslog 服务器来从客户端系统接收日志。

要实时查看日志消息,请运行以下命令:

$ tail -f /var/log/messages

现在开始配置客户端系统。

在 RHEL 8 上配置客户端系统

与 Rsyslog 服务器一样,登录并通过以下命令检查 rsyslog 守护进程是否正在运行:

$ sudo systemctl status rsyslog

示例输出:

client-rsyslog-service-rhel8

接下来,打开 rsyslog 配置文件:

$ sudo vim /etc/rsyslog.conf

在文件末尾,添加以下行:

*.* @10.128.0.47:514           # Use @ for UDP protocol
*.* @@10.128.0.47:514          # Use @@ for TCP protocol

保存并退出配置文件。就像 Rsyslog 服务器一样,打开 514 端口,这是防火墙上的默认 Rsyslog 端口:

$ sudo firewall-cmd  --add-port=514/tcp  --zone=public  --permanent

接下来,重新加载防火墙以保存更改:

$ sudo firewall-cmd --reload

接下来,重启 rsyslog 服务:

$ sudo systemctl restart rsyslog

要在启动时运行 Rsyslog,请运行以下命令:

$ sudo systemctl enable rsyslog

测试日志记录操作

已经成功安装并配置 Rsyslog 服务器和客户端后,就该验证你的配置是否按预期运行了。

在客户端系统上,运行以下命令:

# logger "Hello guys! This is our first log"

现在进入 Rsyslog 服务器并运行以下命令来实时查看日志消息:

# tail -f /var/log/messages

客户端系统上命令运行的输出显示在了 Rsyslog 服务器的日志中,这意味着 Rsyslog 服务器正在接收来自客户端系统的日志:

centralize-logs-rsyslogs-centos8

就是这些了!我们成功设置了 Rsyslog 服务器来接收来自客户端系统的日志信息。


via: https://www.linuxtechi.com/configure-rsyslog-server-centos-8-rhel-8/

作者:James Kiarie 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出