你不应该使用root用户在容器中启动程序,或者类似的特权用户,你应该在Dockerfile中创建一个普通用户,并且制定一个特别的uid和gid,然后用这个账户来启动进程,这样更加容易限制容器访问资源的权限。

预览

根据Linux的最低权限原则,一个程序应该只能访问他运行所必须的资源。这是一件非常严肃的事情。运行在系统上可能是一个病毒也可能是一个包含bug的程序,会对他能访问到的资源造成破坏。为了保证系统的安全稳定,你应该限制应用能够访问的资源和能够获取的权限。

许多容器化的程序并不要求使用root权限,docker本身也不要求使用root权限,所以编写一个安全和服用的镜像,不能期望容器将来会使用root权限运行,为了易于控制权限,你也不应该在镜像中使用root。

为什么

在使用Docker的时候,你需要清楚,容器中的进程和宿主机器上的其进程是没有区别的,他们使用的是同一个内核,受同一个内核管理权限,如果说容器中的进程是root权限,那么也就意味这这个进程在宿主机器上其实也是root权限。如果资源被挂载到容器中,那么意味着容器中的进程能够获取,修改,删除这些资源,因为它是root权限。

如果你要创建一个镜像,那么你就应该在Dockerfile中创建一个默认的用户,而不是使用默认的root。这样更加容器控制权限。我们举一个root容器的例子,看一下他的危害:

我在宿主机器的root目录下创建一个隐私文件,我使用root用户创建,只有root权限的用户才能访问这个文件

[email protected]:~$ sudo -s
[email protected]:~# cd /root
[email protected]:~# echo "top secret stuff" >> ./secrets.txt 
[email protected]:~# chmod 0600 secrets.txt
[email protected]:/root# ls -l
total 4
-rw------- 1 root root 17 Sep 26 20:29 secrets.txt
[email protected]:/root# exit
exit
[email protected]:~$ cat /root/secrets.txt
cat: /root/secrets.txt: Permission denied

现在我退出root,使用一个普通的用户来创建一个Dockerfile:

FROM debian:stretch
CMD ["cat", "/tmp/secrets.txt"]
然后我编译运行这个镜像,并且将我们的隐私文件挂载进去:

[email protected]:~$ docker run -v /root/secrets.txt:/tmp/secrets.txt <img>
top secret stuff

即便我在宿主机器上的容器是marc,但是在容器中,我已经拥有root权限了,能够访问宿主机上root权限才能访问的资源文件。这也就是说任何人在DockerHub上下载这镜像,都会暴露自己的特权文件(当然,还取决于你怎么它)。

建议
我建议在创建Dockerfile的时候创建一个普通用户,并且指定uid和gid。使用这个普通用户来启动容器。

FROM <base image>
RUN groupadd -g 999 appuser && \
    useradd -r -u 999 -g appuser appuser
    USER appuser
    ... <rest of Dockerfile> ...

在上面的例子,我们能够非常用户控制容器用户和用户的权限。

更具上面的例子,我修改一下Dockerfile

FROM debian:stretch
RUN groupadd -g 999 appuser && \
    useradd -r -u 999 -g appuser appuser
    USER appuser
    CMD ["cat", "/tmp/secrets.txt"]

再次上之前那样运行容器

[email protected]:~$ docker run -v /root/secrets.txt:/tmp/secrets.txt <img>
cat: /tmp/secrets.txt: Permission denied

这次隐私文件得到了保护。

复用其他容器

Docker容器非常有好的地方是他可以能够复用,你能够使用FROM来使用一个已经存在的容器。你能够创建你自己的用户,或者是root用户(前提是你确实需要root用户)。如果某些镜像中默认使用了root用户,你可以在自己的Dockerfile中覆盖这个设置,使用你自己的创建的用户。

全新安装 laravel6,访问时报错:

Declaration of Symfony\Component\Translation\TranslatorInterface::setLocale($locale) must be 

compatible with Symfony\Contracts\Translation\LocaleAwareInterface::setLocale(string $locale)

查看资料发现该问题和环境有关系,解决办法如下:

修改 composer.json 文件, 添加:

"require": {
     ...
     "symfony/translation": "4.3.8",
}

运行

composer update

即可~

一、数据表使用 uuid 作为主键,需要注意哪些事情?

可以在 boot 方法中增加生成 uuid 为主键的方法。如果在生成主键之后需要获取 id,那么需要设置 $incrementing = false。具体如下:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Uuid;

class Order extends Model
{

    public $incrementing = false;

    /**
     *  Setup model event hooks
     */
    public static function boot()
    {
        parent::boot();
        self::creating(function ($model) {
            $model->id = (string) Uuid::generate(4);
        });
    }
}

二、路由变量含有“/”,该如何定义路由?

有时候,我们需要在路由中传入复杂的变量,比如一段路径,这个时候可以通过在 routeServiceProvider 中定义一个 pattern 来实现变量被正确的解析。

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{

    protected $namespace = 'App\Http\Controllers';

    public function boot()
    {
        //
        Route::pattern('path', '[a-zA-Z0-9-_/.:\?]+');
        parent::boot();
    }

}

这样,比如有以下一张动态剪裁的图片,图片的 path 为:

images/201911/bd484d1ef139b1d7f257d6f96baa3ac2.jpeg

构造的 url 为:

http://inspection.test/image/500x335/90/1/c296c89eb4c0fe68/images/201911/bd484d1ef139b1d7f257d6f96baa3ac2.jpeg

路由就可以这样定义了:

Route::get('/image/{width}x{height}/{quality}/{type}/{signature}/{path}', "[email protected]");

三、laravel 的 访问控制策略

laravel 提供了针对模型数据访问控制的方法,称之为 Policy,通过定义 Policy,我们可以非常方便的解决细粒度的权限问题。

比如,我们允许用户编辑,删除自己的文章,而不能编辑和删除他人的文章,这个该怎么做呢?

一般情况下,我们需要在控制器的代码段中做一下判断,判断一下该文章是否属于该用户,在这里 laravel 帮我们做了很好的封装,可以非常方便的定义这样的访问控制

3.1 第一步: 生成策略
使用 artisan 命令,可以生成一个策略类,该类默认被写入到 AppPolicies 目录下:

php artisan make:policy PostPolicy

所生成的类,大致上是这样的:

<?php

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    /**
     * 判断该方法能否被用户操作。
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return bool
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

3.2 第二步: 注册策略

一旦策略存在,它就需要进行注册。新的 Laravel 应用中包含的 AuthServiceProvider 有一个 policies 属性,可以将各种模型对应到它们的策略中。注册一个策略将引导 Laravel 在授权动作访问指定模型的时候使用哪种策略:

<?php

namespace App\Providers;

use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 应用的策略映射。
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * 注册任意应用认证、应用授权服务
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
    }
}

3.3 第三步: 执行策略

  • 通过控制器:
$this->authorize('create', Post::class);
  • 通过中间件:
Route::post('/post', '[email protected]')->middleware('can:update,post');
  • 通过模版:
@can('update', $post)
    <!-- The Current User Can Update The Post -->
@elsecan('create', App\Post::class)
    <!-- The Current User Can Create New Post -->
@endcan

一、PBN 是什么?

PBN 是英文 Private Blog Network 的缩写,中文意思大致是“秘密博客网络”,建立多个站点,形成一个网络,然后用这个网络给目标网站建立外链,用于给目标网站的页面增加权重。因为这属于 SEO 作弊,不希望被搜索引擎发现,所以号称是秘密的。

PBN,在国内也被称为站群,是一种高风险高投入高回报的 SEO 手段,属于黑帽SEO的范畴,具有被 Google K站的风险,当然,这种方法所带来的效果也非常明显,是有效的 SEO 高级手段。

但是,不得不说,这条路难度系数也很高,需要很多资源的支持。

二、PBN 的基本原理

那么 PBN 是怎么样给目标网站增加权重呢?

核心思想是这样子:PBN 中的每个站的域名都会有权重。通过在这个站做直接的外链去目标网站的目标页面,就能把这个站本身的权重值传递给目标站。一般是直接用一级外链指向目标网站,为了安全起见,可以选择把站群的外链作为二级外链,从而进一步规避风险。

三、PBN 怎么做?

不得不说,这是一件非常复杂且非常讲究的事情,大体上来说,至少包含以下几个方面:

  1. 购买高权重域名
  2. 购买服务器资源
  3. 制作站点
  4. 部署站点
  5. 通过链接传递权重

删除文件,空间并未释放

linux某个目录空间快满了,删除了若干的文件后,使用 df -h 显示还是快满的,但是 df -h * 显示的总的文件大小又没那么大。

某个进程正在使用删除的文件,导致删除后,空间仍然不能释放。

查看rm掉但是仍被占用的文件的列表,使用如下命令:

lsof |grep -i deleted

再使用 ps -aux | grep pid 查到对应的进程号,关闭进程或者 kill 进程,再次查看空间已释放。

lsof命令是什么?

可以列出被进程所打开的文件的信息。被打开的文件可以是

1.普通的文件
2.目录
3.网络文件系统的文件
4.字符设备文件
5.(函数)共享库
6.管道,命名管道
7.符号链接
8.底层的socket字流,网络socket,unix域名socket
9.在linux里面,大部分的东西都是被当做文件的…..还有其他很多

怎样使用lsof

这里主要用案例的形式来介绍lsof 命令的使用

1.列出所有打开的文件:

lsof

备注: 如果不加任何参数,就会打开所有被打开的文件,建议加上一下参数来具体定位

  1. 查看谁正在使用某个文件

lsof /filepath/file

3.递归查看某个目录的文件信息

lsof +D /filepath/filepath2/

备注: 使用了+D,对应目录下的所有子目录和文件都会被列出

  1. 比使用+D选项,遍历查看某个目录的所有文件信息 的方法

lsof | grep ‘/filepath/filepath2/’

  1. 列出某个用户打开的文件信息

lsof -u username

备注: -u 选项,u其实是user的缩写

  1. 列出某个程序所打开的文件信息

lsof -c mysql

备注: -c 选项将会列出所有以mysql开头的程序的文件,其实你也可以写成 lsof | grep mysql, 但是第一种方法明显比第二种方法要少打几个字符了

  1. 列出多个程序多打开的文件信息

lsof -c mysql -c apache

  1. 列出某个用户以及某个程序所打开的文件信息

lsof -u test -c mysql

  1. 列出除了某个用户外的被打开的文件信息

lsof -u ^root

备注:^这个符号在用户名之前,将会把是root用户打开的进程不让显示

  1. 通过某个进程号显示该进行打开的文件

lsof -p 1

  1. 列出多个进程号对应的文件信息

  lsof -p 123,456,789

  1. 列出除了某个进程号,其他进程号所打开的文件信息

  lsof -p ^1

13 . 列出所有的网络连接

  lsof -i

  1. 列出所有tcp 网络连接信息

  lsof -i tcp

  1. 列出所有udp网络连接信息

  lsof -i udp

  1. 列出谁在使用某个端口

  lsof -i :3306

  1. 列出谁在使用某个特定的udp端口

  lsof -i udp:55

特定的tcp端口

  lsof -i tcp:80

  1. 列出某个用户的所有活跃的网络端口

  lsof -a -u test -i

  1. 列出所有网络文件系统

  lsof -N

20.域名socket文件

  lsof -u

  21.某个用户组所打开的文件信息

  lsof -g 5555

  22. 根据文件描述列出对应的文件信息

  lsof -d description(like 2)

  23. 根据文件描述范围列出文件信息

  lsof -d 2-3