基于开源OPA实现Kubernetes资源安全合规基线检查_第1页
基于开源OPA实现Kubernetes资源安全合规基线检查_第2页
基于开源OPA实现Kubernetes资源安全合规基线检查_第3页
基于开源OPA实现Kubernetes资源安全合规基线检查_第4页
基于开源OPA实现Kubernetes资源安全合规基线检查_第5页
已阅读5页,还剩29页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

基于开源OPA实现Kubernetes资源安全合规基线检查

【摘要】本文首先简单介绍了Kubernetes的资源治理问题以及准入控制器,然后介绍了OPA以及Rego,最后介绍如何将OPA集成到Kubernetes的准入控制器中实现Kubernetes资源的安全合规自动检测。1Kubernetes资源治理问题近年来越来越多的企业选择使用容器用于软件构建和发布,随着DockerSwarm的逐渐没落,毫无疑问Kubernetes已经成为事实上的容器编排工具标准。目前使用Kubernetes已经不是什么大的难题,基本上几个命令几个yaml文件就能轻易地使应用运行在Kubernetes平台上。而对于Kubernetes平台运维人员来说关键的挑战在于如何用好Kubernetes以及如何高效治理Kubernetes的资源。因为往往企业应用部署和平台运维是独立负责的,极力推广的DevOps理念大大提升了开发人员的自主性,但是开发人员往往只关注于应用如何部署运行,而很少关注一些跟应用没有直接关系的诸如安全、可靠性、资源配比等因素。比如:开发人员可能错误配置liveness以及readiness,甚至根本不配置。开发人员可能不会想到去合理配置limits以及requests资源,或者错误随意配置limits以及requests资源,设置不合理的比例。开发人员可能过度滥用容器的capacities权限,容器内部OS用户随意使用root,volume的fsuser、fsgroup设置的uid、gid不合理等。未设置企业要求的一些元数据标签labels或者注解annotations。Pod使用未受信任的镜像仓库以及大量使用latest标签的镜像。而解决如上问题,最有效的方法就是设置准入门槛,不符合规范的请求直接拒绝,而不是先污染后治理。Kubernetes一开始设计就支持通过准入控制器(AdmissionControllers)方案设置准入门槛,官方也提供了许多现成的准入控制器供用户选择使用。但是不同的企业可能安全准则、应用最佳实践标准并不一样,现有的准入控制器可能无法完全满足企业的所有需求,重新自研开发控制器则涉及开发成本高,并且后期也不易于维护扩展。因此,我认为最有效的办法是能通过统一标准的规则引擎实现合规基线检测和修正,针对不同的规范要求,只需要编写对应规则动态注入,无需大量开发控制器插件,无需重启Kubernetes平台。本文接下来将介绍基于开源OPA实现Kubernetes资源合规基线检查方案。2关于Kubernetes准入控制虽然本文主要介绍如何使用OPA执行Kubernetes资源合规基线检查,但其原理离不开Kubernetes的准入控制,因此本文首先简单介绍下Kubernetes自带的准入控制机制。众所周知,我们每次请求KubernetesAPI时都会经过APIServer的三大核心关口,层层把关:第一个关口是认证,判定请求用户是谁以及其身份的真实性。关于认证的介绍可以参考我之前的几篇文章浅聊Kubernetes的各种认证策略以及适用场景以及Kubernetes通过Dex集成企业外部认证系统。第二个关口是授权,判定这个用户是否具有该操作的权限。目前用的比较多的是默认的RBAC插件,即通过role、clusterrole、rolebingding以及clusterrolebingding进行权限访问控制。第三个关口是准入控制。准入控制是对请求的进一步控制,比如检查请求的资源数量是否超出了配额(Quota)限制,请求的Pod是否符合容器安全要求等。kubeapi_pilelines每个关口都有可能存在多个保安插件,Kubernetes支持配置多个认证以及授权引擎从而支持多认证多授权模式。同理,Kubernetes可以配置多个准入控制器,只有所有准入控制器都判定通过时,请求方可放行。另外准入控制器还支持对请求的资源进行修改,比如AlwaysPullImages准入控制器会强制修改Pod的imagePullPolicy为Always。Kubernetes的准入控制与认证授权的设计理念相同,都是通过动态扩展插件的形式实现,每个插件依次串接。官方提供了许多现成的非常实用的准入控制器,比如:LimitRanger:对请求的CPU、Memory等资源进行范围限制,阻止请求超出设置范围的CPU数量或者内存大小。ResourceQuota:对Namespace的CPU、内存、Pod数量等资源进行配额限制,如果请求超出Namespace配额,则拒绝服务。PodSecurity:替换即将被废弃的PodSecurityPolicy,在v1.23中升为Beta版,对Pod进行安全限制,比如Capabilities、Privileged等。ValidatingAdmissionWebhook:与认证授权一样,准入控制也支持外部Webhook模式,apiserver会把请求体转发给外部Webhook,由外部的Webhook进行评审判定,返回判定结果。更多的准入控制器可以参考官方文档UsingAdmissionControllers[1]。当然用户也可以根据自己的需求实现自定义准入控制器。3OpenPolicyAgent简介3.1OpenPolicyAgentOpenPolicyAgent(OPA)[2]是一款开源的通用策略规则引擎工具,提供PolicyasService服务,通过统一标准的高级声明式查询语言Rego定义规则,基于规则和输入数据输出判定结果:opa-service比如最典型的基于角色的权限控制(RBAC)就可以抽象成策略模型:定义各个角色具有哪些权限,通过规则判断用户隶属于哪个角色,从而判定该用户具备哪些权限,比如:如果用户属于角色admin,则允许GET、PUT、POST、DELETE等所有操作。如果用户属于角色readonly,则只允许GET操作。如果用户不属于任何角色,则不允许任何操作。还有一个典型的场景便是安全合规性检测,比如我们定义规则Kubernetes的所有Pod不允许使用latest标签镜像,通过这个规则可以查找出所有包含latest标签的违规Pod。我们还可以把这个规则设置为准入门槛,每次创建Pod前,都会通过我们的规则去做评审(Review),如果包含latest镜像则直接拒绝服务。做策略引擎的,除了OPA,还有之前介绍的CloudCustodian[3](c7n),相对OPA,c7n主要面向公有云服务的合规检测,不同的公有云厂商需要绑定不同的provider,目前实现比较完善的provider是AWS。关于c7n的介绍可以参考我之前的一篇文章云资源安全合规基线自动化检查与配置。而OPA的设计理念则是软件服务本体与策略、策略与决策完全松耦合,换句话说,通过标准统一的策略决策,与检测体完全独立,只要检测对象能够转化为标准输入数据源即可。3.2典型场景官方提供了几个典型的应用场景:Docker权限访问控制[4]。Docker目前尚不支持严格的授权访问控制,只要用户能访问DockerAPI,就可以对所有容器进行CURD操作。Docker通过安装opa-docker-authz插件,可实现基于用户角色的权限控制。除此之外还可以实现容器的合规性检测,最典型的应用场景比如检测容器是否使用了高危的capabilities或者未配置某个必须的SecurityOptions。HTTP服务RBAC控制[5]。通过OPA就不需要再去重复实现复杂的RBAC控制逻辑了,直接交给OPA就可以实现完备的权限访问控制。TerraformTF模板文件合规基线检查[6]。检查Terraform模板生成的资源是否满足合规基线要求,比如是否打上了必须的tag,安全组是否开启高危端口等。国内厂商云霁开源的CloudIaC[7]项目正是使用了OPA实现Terraform模板的安全合规基线检查。结合LinuxPAM实现更细粒度的SSH以及sudo的权限控制[8]。对于没有使用堡垒机的场景,可以考虑这种过渡方案。Kafka[9]以及Envoy[10]的权限访问控制。Kubernetes的资源合规基线检查[11],这也是本文将要详细介绍的方案。3.3Rego语法快速入门使用OPA,最核心的工作便是编写规则,而OPA使用的是Rego语言进行规则定义,因此不妨先简单介绍下Rego。Rego是基于Datalog[12]扩展的声明式查询语言,其主要优化了JSON结构化数据的处理和查询能力。虽然和SQL一样都属于声明式查询语言这一语言类别,但是使用起来却更像编程语言,尤其是Python。关于Rego的详细语法可以参考官方文档policy-language[13],这里只简单介绍一些最简单的语法。Rego最基本的规则rule模板格式如下:x

=

y

{

expr_1

expr_2

...

}如上所有表达式expr均为AND关系,即当且仅当所有的expr判定结果为true时,x会被置为y。比如下面这条Rule,当x>y且x>z时,x_is_greatest为true:x_is_greatest

=

true

{

x

>

y

x

>

z

}后面变量赋值=true为默认值,可以省略,因此如下写法的效果是完全一样的:x_is_greatest

{

x

>

y

x

>

z

}这有点类似Python的语法:x_is_greatest

=

True

if

x

>

y

and

x

>

zRego不支持显式的OR逻辑语法,但是可以通过声明两个名称相同的并列Rule定义以支持OR语义:default

allow

=

false

allow

{

input.user

==

"admin"

}

allow

{

input.user

==

"Jim"

input.method

==

"GET"

}如上定义了两个相同的Rule

allow,当满足任意两个条件之一时,allow为true:user为admin;user为Jim,且method为GET。因此有如下判定结果:allow

with

input

as

{"user":

"admin",

"method":

"POST"}

#

true

allow

with

input

as

{"user":

"Jim",

"method":

"GET"}

#

true

allow

with

input

as

{"user":

"Jim",

"method":

"POST"}

#

false除了OR,Rego也不像大多数编程语言一样具有丰富的分支选择语法(if-else),但是可以通过相同的方式实现if-else逻辑,比如实现f(x)函数:f(x)=A,如果x>=90;f(x)=B,如果80<=x<90;f(x)=C,如果70<=x<80;f(x)=D,如果x<70f(x)

=

"A"

{

x

>=

90

}

f(x)

=

"B"

{

x

>=

80;

x

<

90

}

f(x)

=

"C"

{

x

>=

70;

x

<

80

}

f(x)

=

"D"

{

x

<

70

}前面介绍的语法还是比较容易理解的,Rego与大多数编程语言在使用方面最大的差别在于集合的操作以及迭代遍历,初次使用会感觉特别奇怪。比如arr[i],如果i为已定义的变量,则与大多数编程语言语义相同,都是取数组对应索引的值。但是当i未定义时,则表示遍历数组的索引和值,类似Python的enumerate()函数,在后面的迭代过程中可以引用索引值i以及对应数组的值。>

arr

:=

["a",

"b",

"a",

"c"]

>

arr[i]

+---+--------+

|

i

|

arr[i]

|

+---+--------+

|

0

|

"a"

|

|

1

|

"b"

|

|

2

|

"a"

|

|

3

|

"c"

|

+---+--------+

>

i

:=

2

>

arr[i]

"a"特别地,当变量名为_时,则表示忽略索引值:>

arr

:=

["a",

"b",

"a",

"c"]

>

arr[_]

+--------+

|

arr[_]

|

+--------+

|

"a"

|

|

"b"

|

|

"a"

|

|

"c"

|

+--------+因此在Rego中遍历数组的方法如下:containers

:=

["/nginx:v1.7",

"/mysql:5.7"]

container

:=

containers[_]

not

startswith(container,

"/")

...注:如上空格缩进仅为方便理解,实际编写时不需要缩进。语义类似Python的如下写法:containers

=

["/nginx:v1.7",

"/mysql:5.7"]

for

container

in

containers:

if

not

container.startswith("/"):

...当然新版本的Rego也支持了in语法,如上代码可等效为:containers

:=

["/nginx:v1.7",

"/mysql:5.7"]

some

container

in

containers

not

startswith(container,

"/")

...注:如上some关键字表示声明一个局部变量。根据这种语法进一步引申,判断元素是否包含在列表中,看起来也会比较奇怪:>

arr

:=

["a",

"b",

"c",

"b"]

>

arr[i]

==

"c"

#

如果i变量未定义,则会遍历数组,返回所有值为c的索引列表

+---+

|

i

|

+---+

|

2

|

+---+

>

i

:=

1

>

arr[i]

==

"c"

#

i已定义为数字1,则判断指定索引1中的值是否为c

false引入了集合后,派生出一种新的规则rule语法,样例如下:containers

:=

["/nginx:v1.7",

"/mysql:5.7"]

deny_images[c]

{

container

:=

containers[_]

not

startswith(container,

"/")

c

:=

container.image

}如上的deny_images是一个列表,语义为遍历containers列表,找出不是以/为前缀的元素,加到deny_images中,等效Python语法:containers

=

["/nginx:v1.7",

"/mysql:5.7"]

deny_images

=

[]

for

container

in

containers:

if

not

container.startswith("/"):

c

=

container

deny_images.append(c)为什么会有如上奇怪的语法?个人感觉Rego的语法虽然理解起来可能比较绕,但是针对集合操作与查询写起来会比Python更简练些。与Python一样,Rego也支持集合推导(Comprehension),比如:arr

:=

["a",

"b",

"c",

"a",

"c",

"d"]

s

:=

{i

|

i

=

arr[_]}等价于Python:arr

=

["a",

"b",

"c",

"a",

"c",

"d"]

s

=

{i

for

i

in

arr}除了以上的语法,Rego集成了非常丰富的内置函数,可以参考Regobuilt-in-functions[14],关于Rego的更多详细语法可以参考官方文档policy-language[15]。4Kubernetes集成OPA准入控制器前面介绍了Kubernetes的准入控制以及通用策略规则引擎工具OPA。显然,OPA是非常适合用于实现Kubernetes资源的安全合规基线配置检查的。针对这个场景,OPA官方已提供了现成的集成方案OPAGatekeeper[16],该方案通过Webhook准入控制器方式集成。可以认为OPAGatekeeper是一个代理Web服务,KubernetesAPIServer通过Webhook准入控制器将请求体转发给OPAGatekeeper评审,请求Body样例如下:{

"kind":

"AdmissionReview",

"request":

{

"kind":

{

"kind":

"Pod",

"version":

"v1"

},

"object":

{

"metadata":

{

"name":

"myapp"

},

"spec":

{

"containers":

[

{

"image":

"nginx",

"name":

"nginx-frontend"

},

{

"image":

"mysql",

"name":

"mysql-backend"

}

]

}

}

}

}OPAGatekeeper基于Repo规则对请求的Body进行判定,并返回给APIServer。kubernetesadmissionflow关于OPAGatekeeper的安装配置可以参考官方文档OPAgatekeeperinstall[17],如果只需要默认配置,简单只需要一条apply命令即可:kubectl

apply

-f

\

/open-policy-agent/gatekeeper/release-3.7/deploy/gatekeeper.yaml如上会创建ValidatingWebhookConfiguration、gatekeeper-controller-manager以及相关CRD资源。#

kubectl

get

ValidatingWebhookConfiguration

NAME

WEBHOOKS

AGE

gatekeeper-validating-webhook-configuration

2

47h

#

kubectl

get

pod

-n

gatekeeper-system

NAME

READY

STATUS

RESTARTS

AGE

gatekeeper-audit-545fcc7c99-hgttl

1/1

Running

0

47h

gatekeeper-controller-manager-744959ffc-28tnt

1/1

Running

0

47h

gatekeeper-controller-manager-744959ffc-2jnkt

1/1

Running

0

47h

gatekeeper-controller-manager-744959ffc-47zvz

1/1

Running

0

47h

#

kubectl

api-resources

|

grep

gatekeeper

configs

config.gatekeeper.sh/v1alpha1

true

Config

k8srequiredlabels

constraints.gatekeeper.sh/v1beta1

false

K8sRequiredLabels

providers

externaldata.gatekeeper.sh/v1alpha1

false

Provider

assign

mutations.gatekeeper.sh/v1beta1

false

Assign

assignmetadata

mutations.gatekeeper.sh/v1beta1

false

AssignMetadata

modifyset

mutations.gatekeeper.sh/v1beta1

false

ModifySet

constraintpodstatuses

status.gatekeeper.sh/v1beta1

true

ConstraintPodStatus

constrainttemplatepodstatuses

status.gatekeeper.sh/v1beta1

true

ConstraintTemplatePodStatus

mutatorpodstatuses

status.gatekeeper.sh/v1beta1

true

MutatorPodStatus

constrainttemplates

templates.gatekeeper.sh/v1

false

ConstraintTemplate5利用OPA实现Kubernetes资源合规基线检查5.1规则模板定义前面安装了OPAGatekeeper,创建了相关CRD,其中constrainttemplates是最重要的CRD资源,用于定义规则模板。以一个简单的场景为例,假设我们要求所有的资源必须包含指定的labels,则可定义模板如下:kind:

ConstraintTemplate

metadata:

name:

k8srequiredlabels

spec:

crd:

spec:

names:

kind:

K8sRequiredLabels

validation:

openAPIV3Schema:

properties:

labels:

type:

array

items:

string

targets:

-

target:

admission.k8s.gatekeeper.sh

rego:

|

package

k8srequiredlabels

violation[{"msg":

msg,

"details":

{"missing_labels":

missing}}]

{

provided

:=

{label

|

input.review.object.metadata.labels[label]}

required

:=

{label

|

label

:=

input.parameters.labels[_]}

missing

:=

required

-

provided

count(missing)

>

0

msg

:=

sprintf("you

must

provide

labels:

%v",

[missing])

}constrainttemplates模板定义包含两部分:openAPIV3Schema定义输入参数,上面的例子我们定义了一个数组变量labels。targets定义Rego规则。前面的Rego例子不难理解,首先取出资源Object的labels列表,然后取出要求必须配置的labels列表,最后二者取差集就是缺失的labels列表。5.2规则实例化及应用配置了模板后,需要实例化constraint,引用前面的constrainttemplate并配置输入参数:apiVersion:

constraints.gatekeeper.sh/v1beta1

kind:

K8sRequiredLabels

metadata:

name:

namespace-labels-check

spec:

match:

kinds:

-

apiGroups:

[""]

kinds:

["Namespace"]

parameters:

labels:

["app_name",

"org_name"]constraint定义包含两个主要部分:match:配置需要执行该策略的资源,支持基于namespace白名单、namespace黑名单、label、name等过滤。parameters:配置constrainttemplate中需要的输入参数。创建如上资源后,我们尝试创建没有任何label的namespace:#

kubectl

create

ns

aaa

Error

from

server

:

you

must

provide

labels:

{"app_name",

"org_name"}

admission

webhook

"validation.gatekeeper.sh"

denied

the

request符合预期创建namespace失败,错误中提示没有配置app_name以及org_name标签。5.3查看存量违规资源前面我们设置了K8sRequiredLabelsconstraint,当我们创建没有打app_name以及org_name标签的不合规的namespace时会直接拒绝请求。但是往往在创建K8sRequiredLabelsconstraint之前已经有很多存量的namespaces了,如何查看哪些存量namespaces不合规呢?OPAGatekeeper提供了如下三种方式:通过PrometheusMetrics,指标gatekeeper_violations提供了所有违规的资源列表,这种方式便于与监控告警平台集成。通过AuditLogs,Audit会定时检查不合规的资源并转化成JSON格式输出到stdout中,我们可直接通过查看gatekeeper-audit

Pod的日志即可查看,这种方式便于与日志平台集成。通过ConstraintStatus,OPAGatekeeper会把所有不合规的资源列表放到constraint实例的Status字段中。这种方式便于我们直接查看。如下,我们可以查看所有不满足namespace-labels-check的违规namespaces列表:#

kubectl

get

constraint

namespace-labels-check

-o

yaml

apiVersion:

constraints.gatekeeper.sh/v1beta1

kind:

K8sRequiredLabels

metadata:

name:

namespace-labels-check

spec:

match:

kinds:

-

apiGroups:

-

""

kinds:

-

Namespace

parameters:

labels:

-

app_name

-

org_name

status:

totalViolations:

5

violations:

-

enforcementAction:

deny

kind:

Namespace

message:

'you

must

provide

labels:

{"app_name",

"org_name"}'

name:

kube-node-lease

-

enforcementAction:

deny

kind:

Namespace

message:

'you

must

provide

labels:

{"org_name"}'

name:

nginx-app

-

enforcementAction:

deny

kind:

Namespace

message:

'you

must

provide

labels:

{"app_name",

"org_name"}'

name:

kube-system

-

enforcementAction:

deny

kind:

Namespace

message:

'you

must

provide

labels:

{"app_name",

"org_name"}'

name:

default

-

enforcementAction:

deny

kind:

Namespace

message:

'you

must

provide

labels:

{"app_name",

"org_name"}'

name:

kube-public5.4关于间接创建违规资源处置前面当我们直接创建不合规的namespace时会直接拒绝请求。但是有些资源我们不会直接创建,而是间接创建。比如我们一般不会直接创建Pod,而是往往通过Deployments或者DaemonSets间接创建Pod资源。当我们的规则限制作用于Pod时,则无法直接拒绝创建违规的Deployments或者DaemonSets,但创建的Deployment会创建Pod失败。比如我们创建如下的constraint实例,要求所有的Pod必须包含app_name以及org_name标签。apiVersion:

constraints.gatekeeper.sh/v1beta1

kind:

K8sRequiredLabels

metadata:

name:

pod-labels-check

spec:

match:

kinds:

-

apiGroups:

[""]

kinds:

["Pod"]

parameters:

labels:

["app_name",

"org_name"]我们创建nginx-appDeployment:apiVersion:

apps/v1

kind:

Deployment

metadata:

labels:

app:

nginx-app

name:

nginx-app

spec:

replicas:

3

selector:

matchLabels:

app:

nginx-app

template:

metadata:

labels:

app:

nginx-app

spec:

containers:

-

image:

nginx:latest

name:

nginx此时Deployment创建成功,但是Pod不会被创建出来:#

kubectl

get

deployments.apps

NAME

READY

UP-TO-DATE

AVAILABLE

AGE

nginx-app

0/3

0

0

3m49s我们可以查看最近的Event:#

kubectl

get

events

LAST

SEEN

TYPE

REASON

OBJECT

MESSAGE

2m31s

Warning

FailedCreate

replicaset/nginx-app-78b5f9b5fd

Error

creating:

admission

"pod-label-check"

denied

the

request:

you

must

provide

labels:

{"app_name",

"org_name"}可见是由于pod-label-check阻断了Pod资源创建。当然我们也可以通过status获取阻断原因:status:

conditions:

-

lastTransitionTime:

"2021-12-30T10:15:32Z"

lastUpdateTime:

"2021-12-30T10:15:32Z"

message:

'admission

webhook

"validation.gatekeeper.sh"

denied

the

request:

[pod-labels-check]

you

must

provide

labels:

{"app_name",

"org_name"}'

reason:

FailedCreate

status:

"True"

type:

ReplicaFailure5.5违规处理前面当我们创建不合规的资源时会直接拒绝请求,这是因为OPA的默认违规处理方式是deny,即拒绝请求。除了deny阻断模式,OPAGatekeeper还支持如下两种违规处理策略:dryrun:仿真模式,Gatekeeper只记录违规的资源列表,但不会阻断违规资源的创建。warn:警告模式,和仿真模式一样不会阻断违规资源的创建,但是会返回告警信息。该模式要求Kubernetes的版本为v1.19+。以warn为例,我们创建一个违规Pod:#

kubectl

apply

-f

pod.yaml

Warning:

[prod-repo-is-openpolicyagent]

container

<nginx>

has

an

invalid

image

repo

"",

allowed

repos

are

[""]

pod/pause

created我们发现Pod创建成功,但是kubectl返回了Warning信息,提示Pod使用了违规的镜像仓库。5.6违规资源自动修正Gatekeeper除了支持基于规则执行违规资源检测,还支持对违规资源进行自动修复。不过目前该功能还不是很完善,只支持静态赋值,不支持动态配置。比如我们要求除kube-system

namespace以外的所有Pod的imagePullPolicy必须为Always,则可以创建如下Assign实例:apiVersion:

mutations.gatekeeper.sh/v1beta1

kind:

Assign

metadata:

name:

set-image-pull-policy-to-always

spec:

applyTo:

-

groups:

[""]

kinds:

["Pod"]

versions:

["v1"]

match:

scope:

Namespaced

kinds:

-

apiGroups:

["*"]

kinds:

["Pod"]

excludedNamespaces:

["kube-system"]

location:

"spec.containers[name:*].imagePullPolicy"

parameters:

assign:

value:

Always另外一个场景是不允许用户设置Pod的securityContext为privileged,则可以通过自动设置securityContext.privileged为false实现。apiVersion:

mutations.gatekeeper.sh/v1beta1

kind:

Assign

metadata:

name:

demo-privileged

spec:

applyTo:

-

groups:

[""]

kinds:

["Pod"]

versions:

["v1"]

match:

scope:

Namespaced

kinds:

-

apiGroups:

["*"]

kinds:

["Pod"]

namespaces:

["bar"]

lo

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论