• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.libraries.pcc.chronicle.analysis.impl
18 
19 import com.android.libraries.pcc.chronicle.analysis.ChronicleContext
20 import com.android.libraries.pcc.chronicle.analysis.ManagementStrategyComparator.compare
21 import com.android.libraries.pcc.chronicle.analysis.PolicyEngine
22 import com.android.libraries.pcc.chronicle.api.Connection
23 import com.android.libraries.pcc.chronicle.api.ConnectionRequest
24 import com.android.libraries.pcc.chronicle.api.DataType
25 import com.android.libraries.pcc.chronicle.api.DataTypeDescriptor
26 import com.android.libraries.pcc.chronicle.api.FieldType
27 import com.android.libraries.pcc.chronicle.api.SandboxProcessorNode
28 import com.android.libraries.pcc.chronicle.api.isWriteConnection
29 import com.android.libraries.pcc.chronicle.api.policy.Policy
30 import com.android.libraries.pcc.chronicle.api.policy.PolicyField
31 import com.android.libraries.pcc.chronicle.api.policy.UsageType
32 import com.android.libraries.pcc.chronicle.api.policy.builder.PolicyCheck
33 import com.android.libraries.pcc.chronicle.api.policy.builder.PolicyCheckResult
34 import com.android.libraries.pcc.chronicle.api.policy.canEgress
35 
36 /**
37  * Uses the policy engine from Arcs to check policy adherence.
38  *
39  * When [checkPolicy] is called, this implementation will perform the following:
40  *
41  * 1. Verify all the fields requested by the connection is marked as allowed for egress.
42  * * Return [PolicyCheckResult.Pass] if the [ManagementStrategies] for the [ConnectionProviders]
43  * being used by the `connectionRequester` are valid for the [Policy], and all of the checks
44  * performed by data flow analysis pass.
45  * * Return [PolicyCheckResult.Fail] otherwise.
46  */
47 class ChroniclePolicyEngine : PolicyEngine {
48   override fun checkPolicy(
49     policy: Policy,
50     request: ConnectionRequest<*>,
51     context: ChronicleContext,
52   ): PolicyCheckResult {
53     // Verify the ManagementStrategies involved against the Policy, and verify the checks using
54     // the results of the data-flow analysis.
55     val violations =
56       policy.verifyManagementStrategies(context.connectionProviders) +
57         checkAllFieldsAreAllowedForEgress(context, policy, request) +
58         policy.verifyContext(context.connectionContext)
59 
60     return PolicyCheckResult.make(violations)
61   }
62 
63   override fun checkWriteConnections(context: ChronicleContext): PolicyCheckResult {
64     val violations =
65       context.connectionProviders.flatMap { checkWriteConnectionManagement(it.dataType, context) }
66 
67     return PolicyCheckResult.make(violations)
68   }
69 
70   private fun checkWriteConnectionManagement(
71     dataType: DataType,
72     context: ChronicleContext
73   ): List<PolicyCheck> {
74     // If the managed data type doesn't declare any write connections, there's nothing to do here.
75     if (dataType.connectionTypes.none { it.isWriteConnection }) return emptyList()
76 
77     // Find the canonical strategy from the policy set for the data type. If we had no strategy
78     // laid out for the data type by any known policy; this is a problem.
79     val managementStrategies = context.policySet.findManagementStrategies(dataType.descriptor)
80     if (managementStrategies.isEmpty()) {
81       return listOf(PolicyCheck("h:${dataType.descriptor.name} is $MUST_HAVE_POLICY_PREDICATE"))
82     }
83 
84     // If the provider's management strategy is less restrictive than all of management strategies,
85     // associated with a data type, that's a problem.
86     if (managementStrategies.all { compare(dataType.managementStrategy, it) > 0 }) {
87       return listOf(
88         PolicyCheck(
89           "h:${dataType.descriptor.name} is $MANAGEMENT_STRATEGY_NOT_STRICT_ENOUGH_PREDICATE"
90         )
91       )
92     }
93     return emptyList()
94   }
95 
96   private fun <T : Connection> checkAllFieldsAreAllowedForEgress(
97     context: ChronicleContext,
98     policy: Policy,
99     request: ConnectionRequest<T>,
100   ): List<PolicyCheck> {
101     val dtd =
102       context.findDataType(request.connectionType)
103         ?: return listOf(
104           PolicyCheck("s:${request.connectionType} is $CONNECTON_NOT_FOUND_PREDICATE")
105         )
106 
107     val policyTarget =
108       policy.targets.find { it.schemaName == dtd.name }
109         ?: return listOf(PolicyCheck("s:${dtd.name} is $DTD_NOT_FOUND_PREDICATE"))
110 
111     val violatingFields = mutableListOf<String>()
112     dtd.fields.forEach { (fieldName, type) ->
113       policyTarget.fields
114         .find { it.fieldPath.last() == fieldName }
115         ?.let { policyField ->
116           violatingFields.addAll(
117             type.findPolicyViolations(
118               "${dtd.name}.$fieldName",
119               policyField,
120               if (request.requester is SandboxProcessorNode) {
121                 { !(it.rawUsages.canEgress() || it.rawUsages.contains(UsageType.SANDBOX)) }
122               } else {
123                 { !it.rawUsages.canEgress() }
124               },
125               context
126             )
127           )
128         }
129         ?: violatingFields.add("${dtd.name}.$fieldName")
130     }
131 
132     return violatingFields.map { PolicyCheck("s:$it is $FIELD_CANNOT_BE_EGRESSED_PREDICATE") }
133   }
134 
135   private fun FieldType.findPolicyViolations(
136     prefix: String,
137     policyField: PolicyField,
138     hasPolicyViolation: (PolicyField) -> Boolean,
139     context: ChronicleContext
140   ): List<String> {
141     return when (this) {
142       FieldType.Boolean,
143       FieldType.Byte,
144       FieldType.ByteArray,
145       FieldType.Char,
146       FieldType.Double,
147       FieldType.Duration,
148       FieldType.Float,
149       FieldType.Instant,
150       FieldType.Integer,
151       FieldType.Long,
152       FieldType.Short,
153       FieldType.String,
154       is FieldType.Enum,
155       is FieldType.Opaque -> if (hasPolicyViolation(policyField)) listOf(prefix) else emptyList()
156       is FieldType.Array ->
157         this.itemFieldType.findPolicyViolations(prefix, policyField, hasPolicyViolation, context)
158       is FieldType.List ->
159         this.itemFieldType.findPolicyViolations(prefix, policyField, hasPolicyViolation, context)
160       is FieldType.Nullable ->
161         this.itemFieldType.findPolicyViolations(prefix, policyField, hasPolicyViolation, context)
162       is FieldType.Nested -> {
163         context.dataTypeDescriptorSet[this.name].findPolicyViolations(
164           prefix,
165           policyField,
166           hasPolicyViolation,
167           context
168         )
169       }
170       is FieldType.Reference ->
171         throw IllegalArgumentException("References are not supported in policy yet.")
172       is FieldType.Tuple ->
173         throw IllegalArgumentException("Tuples are not supported in policy yet.")
174     }
175   }
176 
177   private fun DataTypeDescriptor.findPolicyViolations(
178     prefix: String,
179     policyField: PolicyField,
180     hasPolicyViolation: (PolicyField) -> Boolean,
181     context: ChronicleContext
182   ): List<String> {
183     val violatingFields = mutableListOf<String>()
184     this.fields.forEach { (fieldName, type) ->
185       policyField.subfields
186         .find { it.fieldPath.last() == fieldName }
187         ?.let {
188           violatingFields.addAll(
189             type.findPolicyViolations("$prefix.$fieldName", it, hasPolicyViolation, context)
190           )
191         }
192         ?: violatingFields.add("$prefix.$fieldName")
193     }
194     return violatingFields.toList()
195   }
196 
197   companion object {
198     /**
199      * Predicate with special label used in the [Checks][Check] rendered as errors for a
200      * [PolicyCheckResult.Fail] to let the user know that a given [DataType] does not have policies
201      * matching its [ManagementStrategy].
202      *
203      * Not intended to be propagated with label propagation, it's for error-messaging only.
204      */
205     private const val MUST_HAVE_POLICY_PREDICATE = "\"must have a corresponding policy\""
206 
207     /**
208      * Predicate with special label used in the [Checks][Check] rendered as errors for a
209      * [PolicyCheckResult.Fail] to let the user know that a given [DataType] is insufficiently
210      * restrained according to policy.
211      *
212      * Not intended to be propagated with label propagation, it's for error-messaging only.
213      */
214     private const val MANAGEMENT_STRATEGY_NOT_STRICT_ENOUGH_PREDICATE =
215       "\"management is at least as restrained as the most conservative policy\""
216 
217     /**
218      * Predicate with special label used in the [Checks][Check] rendered as errors for a
219      * [PolicyCheckResult.Fail] to let the user know that a given [Connection], the corresponding
220      * [DataTypeDescriptor] is not registered with [Chronicle].
221      *
222      * Not intended to be propagated with label propagation, it's for error-messaging only.
223      */
224     private const val CONNECTON_NOT_FOUND_PREDICATE = "not a registered connection"
225 
226     /**
227      * Predicate with special label used in the [Checks][Check] rendered as errors for a
228      * [PolicyCheckResult.Fail] to let the user know that a given [Connection], the corresponding
229      * [DataTypeDescriptor] is not found specified in the given [Policy].
230      *
231      * Not intended to be propagated with label propagation, it's for error-messaging only.
232      */
233     private const val DTD_NOT_FOUND_PREDICATE = "not found in the given policy"
234 
235     /**
236      * Predicate with special label used in the [Checks][Check] rendered as errors for a
237      * [PolicyCheckResult.Fail] to let the user know that a given [Connection], the corresponding
238      * field in the [DataTypeDescriptor] is not allowed for [Usage.ANY] in the given [Policy].
239      *
240      * Not intended to be propagated with label propagation, it's for error-messaging only.
241      */
242     private const val FIELD_CANNOT_BE_EGRESSED_PREDICATE = "not allowed for egress"
243   }
244 }
245