版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
基于开源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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024副食品保障供应合同
- 农产品采购合作协议书
- 社区物业管理服务合同
- 小额民间借款合同范本
- 建筑行业材料购销协议模板
- 2023年高考地理复习精题精练-区域发展对交通运输布局的影响(解析版)
- 2024年售房的合同范本
- 建筑工地物资租赁合同书
- 房产抵押担保协议参考
- 2024年劳务协议书样本
- 企业如何利用新媒体做好宣传工作课件
- 如何培养孩子的自信心课件
- 中医药膳学全套课件
- 颈脊髓损伤-汇总课件
- 齿轮故障诊断完美课课件
- 2023年中国盐业集团有限公司校园招聘笔试题库及答案解析
- 大班社会《特殊的车辆》课件
- 野生动物保护知识讲座课件
- 早教托育园招商加盟商业计划书
- 光色变奏-色彩基础知识与应用课件-高中美术人美版(2019)选修绘画
- 前列腺癌的放化疗护理
评论
0/150
提交评论