Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

伪造客户端访问服务端,容易泄漏配置信息 #2099

Closed
ITCCQ opened this issue Mar 29, 2019 · 29 comments
Closed

伪造客户端访问服务端,容易泄漏配置信息 #2099

ITCCQ opened this issue Mar 29, 2019 · 29 comments

Comments

@ITCCQ
Copy link

ITCCQ commented Mar 29, 2019

客户端只需要服务地址和appid就可以获取配置信息,如何鉴别客户端时一个安全的客户端而不是伪造客户端?
建议服务端提供对应的鉴权方式,避免非法客户端连接获取配置

@flysnow911
Copy link

对于安全要求相对较高的公司,这确实是个问题,我司版本已在adminservice, configService中,对于部分服务增加了访问权限控制。

@nobodyiam
Copy link
Member

这块是一直想增强的功能,之前应该也有过类似讨论:

  1. 一种是增加access key机制,在配置项目中设置key,然后拉取配置时也需要填入key,不过这个对API影响较大
  2. 第二种是分别在客户端和服务端增加hook,允许插入自定义逻辑,或许这个方式更通用一些

@Colstuwjx
Copy link

针对第一种方案,能否做成一个开关,用户可以选择配置的项目是否需要认证?

@g5niusx
Copy link

g5niusx commented Apr 1, 2019

这块是一直想增强的功能,之前应该也有过类似讨论:

  1. 一种是增加access key机制,在配置项目中设置key,然后拉取配置时也需要填入key,不过这个对API影响较大
  2. 第二种是分别在客户端和服务端增加hook,允许插入自定义逻辑,或许这个方式更通用一些

是否可以在 apollo 上配置项目的时候,生成一个 token 作为凭证,后续可以通过这个 token 进行操作。每个客户端连接的时候,需要配置一下对应的token。然后这个配置作为一个非必选项配置,来兼容旧的版本。如果 apollo 开启了这个配置项,apollo 就需要客户端提供 token 才能进行配置获取。

@nobodyiam
Copy link
Member

token/access key对于私有的namespace应该是可以的,对于公共的namespace可能就不太适用了

@peng051410
Copy link

请问一下这个功能会在近期进行完善吗?

@joyexpr
Copy link

joyexpr commented Aug 7, 2019

配置中心选型中,wiki看了、issue搜了一圈,发现客户端获取配置居然没有权限控制,这个风险实在太大了。建议至少得在app这个级别可以设置有查看权限的token

@wkcaeser
Copy link
Contributor

wkcaeser commented Aug 7, 2019

生产环境不都是内网么,加上安全组限制就没啥问题吧 如果内网被入侵了,用啥验证都等于白搭

@joyexpr
Copy link

joyexpr commented Aug 7, 2019

@wkcaeser 这个想法太理想了。不考虑生产环境,有的测试环境用了开发方便是对公网开放的。即使是生产环境,也可能存在一个公司或部门用一个统一的配置中心,但不同应用间也不允许互相泄露敏感配置。 如果按内网就够了这个思路,那么所有数据库或中间件都不需要提供权限控制了,显然安全上是不够的,有些需要安全评审的公司,这个问题可能是属于一票否决的

@nobodyiam
Copy link
Member

@joyexpr

很多公司目前的实现都是基于内网可信这个原则,如果有敏感数据比如数据库连接串会特殊处理来提高安全级别(比如加密)。
不过如果apollo能实现一部分能力也是很不错的,这块我们也是一直想做的,你对这块有啥好的建议吗?

@joyexpr
Copy link

joyexpr commented Aug 8, 2019

@nobodyiam 我觉得像上面讨论的第1种思路,如 @g5niusx 补充的,就有一定保障了,至少安全这项不会成为明显的短板。其实我不是很理解,对敏感数据如数据库连接进行加密等处理,那加解密密钥理论上不是也成了一个新的配置(也本该放apollo)吗,如果我们不敢把敏感配置放在配置中心,那这可能本身就是一个问题

@benhouse1987
Copy link

敏感信息集中存储,可能本身就不是一个好主意

@choldrim
Copy link

choldrim commented Oct 11, 2019

正好我们有个需求,需要放开配置中心到公网,因为客户端IP可能会变,不好添加ACL,逛了一圈,没找到apollo-config可以支持鉴权的方式,无奈之下,用nginx做了一个反向代理,把apollo-config放在反向代理的后面,在反向代理模块里面添加Auth验证,这种方案也能满足需求,给大家参考一下,nginx的代理配置:

server{
    listen 80;
    server_name test.my-server.com;

    location /
    {
        auth_basic "auth";
        auth_basic_user_file conf/htpasswd;
        proxy_set_header Authorization '';  # 下面有解析为什么需要修改这个header
        proxy_pass https://apollo-config.my-server.com;
    }
}

proxy_set_header Authorization ''; 说明:
似乎apollo-config判断有Authorization header的时候会尝试做鉴权,然后会导致鉴权失败,因为账号体系不同,所以代理到后端的时候就可以把这个字段去掉了

# 密码文件生成
htpasswd -c -b conf/htpasswd username passwd

访问:

curl -u 'username:passwd' http://test.my-server.com/configs/my-project/default/application/

我们没有用sdk,直接http方式访问,如果用sdk,估计需要hack一下

另外:如果像前面的同学说的内网也不完全可信的话,可以给apollo机器的端口再做ACL,用iptables或者云安全组都行,限制只允许nginx和部分需要机器可以访问apollo-config的端口

@xiaoxiaojushi-ivan
Copy link

xiaoxiaojushi-ivan commented Oct 14, 2019

+1,我们也需要。我们现在是 混合云方案,但跨云专有网打通(涉及5大区域)很贵,以至于我们每个云一套,甚至不同区域还得搞一套。

@nisiyong
Copy link
Member

nisiyong commented Nov 11, 2019

这块是一直想增强的功能,之前应该也有过类似讨论:

  1. 一种是增加access key机制,在配置项目中设置key,然后拉取配置时也需要填入key,不过这个对API影响较大
  2. 第二种是分别在客户端和服务端增加hook,允许插入自定义逻辑,或许这个方式更通用一些

@nobodyiam 看了大家的讨论,主要是对第1种方案提出了补充的建议,第2种方案可能大家不太了解hook具体实现是什么,比较模糊。

目前由于安全审计需求,我们考虑加上这个客户端认证的功能,先总结了大家讨论的几个点:

  1. 在apollo创建项目时能生成token/access key,客户端的拉取配置时需要带上
  2. 为了历史兼容问题,项目下的token有开关可以控制,决定是否开启进行客户端认证
  3. 客户端认证仅对应用的私有namespace做认证,公共namespace不考虑

现在我们准备朝着第1种方案的方向去做,不知道这个对API影响较大考虑点有哪些?或者第2种的hook能具体说明下吗?

@nobodyiam
Copy link
Member

  1. 客户端认证仅对应用的私有namespace做认证,公共namespace不考虑

如果只对私有namespace做认证的话,会简单很多,因为这样其实只要验证应用级别的token就可以了,不需要到namespace级别?那么对API可能影响不大,因为token可以放在配置文件中,比如app.properties或-D参数,就不需要改动获取配置的api接口了,我觉得这个解法应该是现实可行的。

@nisiyong
Copy link
Member

如果只对私有namespace做认证的话,会简单很多,因为这样其实只要验证应用级别的token就可以了,不需要到namespace级别?那么对API可能影响不大,因为token可以放在配置文件中,比如app.properties或-D参数,就不需要改动获取配置的api接口了,我觉得这个解法应该是现实可行的。

嗯,目前具体的细节准备是这么做的:

  1. apollo客户端支持请求时带上token参数。token参数来源properties文件或-D参数或环境变量(容器场景)
  2. configservice添加filter来校验该应用的token,错误则直接返回状态码 401 Unauthorized
  3. ApolloConfigDB库App表添加两列:1)是否启用token校验 2)token列表(多个token字符串,逗号分隔,考虑切换token时兼容问题)

顺哥,您有空的话帮忙看下这样实现是否合理?

@nobodyiam
Copy link
Member

  1. ApolloConfigDB库App表添加两列:1)是否启用token校验 2)token列表(多个token字符串,逗号分隔,考虑切换token时兼容问题)

需要在ApolloPortalDB和ApolloConfigDB库中的App表中都添加“是否启用token校验“

token的话参照ApolloPortalDB.ConsumerToken表创建一个新的表吧,这个表也需要和App一样同步到ApolloConfigDB

另外还有一个点是可以考虑一下是否需要按环境开启”是否启用token校验“

@nisiyong
Copy link
Member

需要在ApolloPortalDB和ApolloConfigDB库中的App表中都添加“是否启用token校验“

token的话参照ApolloPortalDB.ConsumerToken表创建一个新的表吧,这个表也需要和App一样同步到ApolloConfigDB

另外还有一个点是可以考虑一下是否需要按环境开启”是否启用token校验“

有考虑按环境区分,并且也有开关,开关的粒度也是到环境。但是只在ApolloConfigDB创建该表,有点类似ApolloConfigDB.Cluster表(一个应用有多个环境,一个环境有多个cluster)。一个应用可以有多个AccessKeySecret(这个名字是参考阿里云SDK的),以下是该表的DDL,功能还在实现中:

CREATE TABLE `ApolloConfigDB.AccessKey` (
  `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `AppId` varchar(500) NOT NULL DEFAULT 'default' COMMENT 'AppID',
  `Secret` varchar(128) NOT NULL DEFAULT '' COMMENT 'Secret',
  `IsEnabled` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: enabled, 0: disabled',
  `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
  `DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT 'default' COMMENT '创建人邮箱前缀',
  `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
  `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
  PRIMARY KEY (`Id`),
  KEY `AppId` (`AppId`(191)),
  KEY `DataChange_LastTime` (`DataChange_LastTime`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='访问密钥';

@nobodyiam
Copy link
Member

不过这样是不是意味着每个环境的secret都不一样?那这个secret作为一个新的配置项在应用代码中该咋配置?

@nisiyong
Copy link
Member

nisiyong commented Dec 2, 2019

不过这样是不是意味着每个环境的secret都不一样?那这个secret作为一个新的配置项在应用代码中该咋配置?

是的,每个环境secret不一样,对于安全需求来说我们不希望生产环境的secret与其他环境一样。在应用中配置secret的方式现在与app.id的方式保持一致,加载顺序如下:

  1. 系统变量-Dapollo.accesskey.secret。(启动脚本设置,或者利用Spring Profile)
  2. 环境变量APOLLO_ACCESSKEY_SECRET。(启动脚本设置)
  3. 文件/META-INF/app.properties变量apollo.accesskey.secret。(可以利用Maven Profile特性替换不同环境的secret,不适用于一次构建的实践)

Portal上也可以添加accesskey时同时应用到多个环境,正在实现中,等功能好了给顺哥review下,预计最近两周内可以完成。

@nobodyiam
Copy link
Member

是否需要考虑支持secret轮转的场景?比如定期更新secret或者secret上增加过期时间之类的?也就意味着在同一时间可以有两个secret同时生效?

@nisiyong
Copy link
Member

nisiyong commented Dec 3, 2019

@nobodyiam 有的,上述的DDL就可以单个app支持多个secret。在configservice的filter会匹配可用的secret列表。暂时还没考虑到自动过期时间,默认用isEnabled字段来控制secret是否生效

@nobodyiam
Copy link
Member

nobodyiam commented Dec 4, 2019

@nisiyong 所以是否意味着app级别并没有一个开关来设置是否启用访问校验,而是通过是否有secret记录来判断是否启用访问校验?

@nisiyong
Copy link
Member

nisiyong commented Dec 4, 2019

是的,如果该应用有对应的secret并且是isEnabled=true状态,就会开启校验;如果找不到可用的secret,那么就不校验,filter直接空处理,这样也可以保证升级后旧版本保持兼容。

@nisiyong
Copy link
Member

@nobodyiam 顺哥有空帮忙Review下PR😬

@liuzhijiang
Copy link
Contributor

@nisiyong 你好,浏览了下PR。这里解决“伪造客户端访问服务器”的主要手段是检验签名。若一致,则认为请求有效。若不一致,请拒绝请求。有两个问题想请教下,如有不对的地方请指点。1:如何解决请求被抓包。因为请求是明文,被抓包后,数据也就泄露了。2:如何解决重放攻击。

@nisiyong
Copy link
Member

1:如何解决请求被抓包。因为请求是明文,被抓包后,数据也就泄露了。2:如何解决重放攻击。

嗯,这两个问题都有考虑过。

  1. 由于Apollo是内部微服务架构的配置中心,还是基于内网可信的机制。一般在内网,想找一台运维分配给普通业务的机器做网络嗅探,我认为是不太可能做到的。如果真的需要觉得请求响应明文传输的问题,我觉得直接上https即可。
  2. 针对重放攻击,你可以去看看PR里面对头部,有个Timestamp头,可能针对这个客户端发起的时间做时间限制。比如同样的请求在1分钟内可以被重放,过了1分钟后请求就无效了。如果要每个请求只能使用一次的话(加nonce)实现就不够轻量,可能要结合redis来做。所以选择短期内能重放的方案,过期后请求就失效了无法重放。

@nobodyiam
Copy link
Member

solved by #2828 #2883 #2888

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests