• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google LLC nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static com.google.common.base.Preconditions.checkArgument;
35 import static com.google.common.base.Preconditions.checkNotNull;
36 
37 import com.google.api.client.json.GenericJson;
38 import com.google.errorprone.annotations.CanIgnoreReturnValue;
39 import java.util.ArrayList;
40 import java.util.List;
41 import javax.annotation.Nullable;
42 
43 /**
44  * Defines an upper bound of permissions available for a GCP credential via {@link
45  * AccessBoundaryRule}s.
46  *
47  * <p>See <a href='https://cloud.google.com/iam/docs/downscoping-short-lived-credentials'>for more
48  * information.</a>
49  */
50 public final class CredentialAccessBoundary {
51 
52   private static final int RULES_SIZE_LIMIT = 10;
53 
54   private final List<AccessBoundaryRule> accessBoundaryRules;
55 
CredentialAccessBoundary(List<AccessBoundaryRule> accessBoundaryRules)56   CredentialAccessBoundary(List<AccessBoundaryRule> accessBoundaryRules) {
57     checkNotNull(accessBoundaryRules);
58     checkArgument(
59         !accessBoundaryRules.isEmpty(), "At least one access boundary rule must be provided.");
60     checkArgument(
61         accessBoundaryRules.size() < RULES_SIZE_LIMIT,
62         String.format(
63             "The provided list has more than %s access boundary rules.", RULES_SIZE_LIMIT));
64     this.accessBoundaryRules = accessBoundaryRules;
65   }
66 
67   /**
68    * Internal method that returns the JSON string representation of the credential access boundary.
69    */
70   String toJson() {
71     List<GenericJson> rules = new ArrayList<>();
72     for (AccessBoundaryRule rule : accessBoundaryRules) {
73       GenericJson ruleJson = new GenericJson();
74       ruleJson.setFactory(OAuth2Utils.JSON_FACTORY);
75 
76       ruleJson.put("availableResource", rule.getAvailableResource());
77       ruleJson.put("availablePermissions", rule.getAvailablePermissions());
78 
79       AccessBoundaryRule.AvailabilityCondition availabilityCondition =
80           rule.getAvailabilityCondition();
81       if (availabilityCondition != null) {
82         GenericJson availabilityConditionJson = new GenericJson();
83         availabilityConditionJson.setFactory(OAuth2Utils.JSON_FACTORY);
84 
85         availabilityConditionJson.put("expression", availabilityCondition.getExpression());
86         if (availabilityCondition.getTitle() != null) {
87           availabilityConditionJson.put("title", availabilityCondition.getTitle());
88         }
89         if (availabilityCondition.getDescription() != null) {
90           availabilityConditionJson.put("description", availabilityCondition.getDescription());
91         }
92 
93         ruleJson.put("availabilityCondition", availabilityConditionJson);
94       }
95       rules.add(ruleJson);
96     }
97     GenericJson accessBoundaryRulesJson = new GenericJson();
98     accessBoundaryRulesJson.setFactory(OAuth2Utils.JSON_FACTORY);
99     accessBoundaryRulesJson.put("accessBoundaryRules", rules);
100 
101     GenericJson json = new GenericJson();
102     json.setFactory(OAuth2Utils.JSON_FACTORY);
103     json.put("accessBoundary", accessBoundaryRulesJson);
104     return json.toString();
105   }
106 
107   public List<AccessBoundaryRule> getAccessBoundaryRules() {
108     return accessBoundaryRules;
109   }
110 
111   public static Builder newBuilder() {
112     return new Builder();
113   }
114 
115   public static class Builder {
116     private List<AccessBoundaryRule> accessBoundaryRules;
117 
118     private Builder() {}
119 
120     /**
121      * Sets the list of {@link AccessBoundaryRule}'s.
122      *
123      * <p>This list must not exceed 10 rules.
124      *
125      * @param rule the collection of rules to be set, should not be null
126      * @return this {@code Builder} object
127      */
128     @CanIgnoreReturnValue
129     public Builder setRules(List<AccessBoundaryRule> rule) {
130       accessBoundaryRules = new ArrayList<>(checkNotNull(rule));
131       return this;
132     }
133 
134     @CanIgnoreReturnValue
135     public CredentialAccessBoundary.Builder addRule(AccessBoundaryRule rule) {
136       if (accessBoundaryRules == null) {
137         accessBoundaryRules = new ArrayList<>();
138       }
139       accessBoundaryRules.add(checkNotNull(rule));
140       return this;
141     }
142 
143     public CredentialAccessBoundary build() {
144       return new CredentialAccessBoundary(accessBoundaryRules);
145     }
146   }
147 
148   /**
149    * Defines an upper bound of permissions on a particular resource.
150    *
151    * <p>The following snippet shows an AccessBoundaryRule that applies to the Cloud Storage bucket
152    * bucket-one to set the upper bound of permissions to those defined by the
153    * roles/storage.objectViewer role.
154    *
155    * <pre><code>
156    * AccessBoundaryRule rule = AccessBoundaryRule.newBuilder()
157    *   .setAvailableResource("//storage.googleapis.com/projects/_/buckets/bucket-one")
158    *   .addAvailablePermission("inRole:roles/storage.objectViewer")
159    *   .build();
160    * </code></pre>
161    */
162   public static final class AccessBoundaryRule {
163 
164     private final String availableResource;
165     private final List<String> availablePermissions;
166 
167     @Nullable private final AvailabilityCondition availabilityCondition;
168 
169     AccessBoundaryRule(
170         String availableResource,
171         List<String> availablePermissions,
172         @Nullable AvailabilityCondition availabilityCondition) {
173       this.availableResource = checkNotNull(availableResource);
174       this.availablePermissions = new ArrayList<>(checkNotNull(availablePermissions));
175       this.availabilityCondition = availabilityCondition;
176 
177       checkArgument(!availableResource.isEmpty(), "The provided availableResource is empty.");
178       checkArgument(
179           !availablePermissions.isEmpty(), "The list of provided availablePermissions is empty.");
180       for (String permission : availablePermissions) {
181         if (permission == null) {
182           throw new IllegalArgumentException("One of the provided available permissions is null.");
183         }
184         if (permission.isEmpty()) {
185           throw new IllegalArgumentException("One of the provided available permissions is empty.");
186         }
187       }
188     }
189 
190     public String getAvailableResource() {
191       return availableResource;
192     }
193 
194     public List<String> getAvailablePermissions() {
195       return availablePermissions;
196     }
197 
198     @Nullable
199     public AvailabilityCondition getAvailabilityCondition() {
200       return availabilityCondition;
201     }
202 
203     public static Builder newBuilder() {
204       return new Builder();
205     }
206 
207     public static class Builder {
208       private String availableResource;
209       private List<String> availablePermissions;
210 
211       @Nullable private AvailabilityCondition availabilityCondition;
212 
213       private Builder() {}
214 
215       /**
216        * Sets the available resource, which is the full resource name of the GCP resource to allow
217        * access to.
218        *
219        * <p>For example: "//storage.googleapis.com/projects/_/buckets/example".
220        *
221        * @param availableResource the resource name to set
222        * @return this {@code Builder} object
223        */
224       @CanIgnoreReturnValue
225       public Builder setAvailableResource(String availableResource) {
226         this.availableResource = availableResource;
227         return this;
228       }
229 
230       /**
231        * Sets the list of permissions that can be used on the resource. This should be a list of IAM
232        * roles prefixed by inRole.
233        *
234        * <p>For example: {"inRole:roles/storage.objectViewer"}.
235        *
236        * @param availablePermissions the collection of permissions to set, should not be null
237        * @return this {@code Builder} object
238        */
239       @CanIgnoreReturnValue
240       public Builder setAvailablePermissions(List<String> availablePermissions) {
241         this.availablePermissions = new ArrayList<>(checkNotNull(availablePermissions));
242         return this;
243       }
244 
245       /**
246        * Adds a permission that can be used on the resource. This should be an IAM role prefixed by
247        * inRole.
248        *
249        * <p>For example: "inRole:roles/storage.objectViewer".
250        *
251        * @param availablePermission a permission to add, should not be null
252        * @return this {@code Builder} object
253        */
254       public Builder addAvailablePermission(String availablePermission) {
255         if (availablePermissions == null) {
256           availablePermissions = new ArrayList<>();
257         }
258         availablePermissions.add(availablePermission);
259         return this;
260       }
261 
262       /**
263        * Sets the availability condition which is an IAM condition that defines constraints to apply
264        * to the token expressed in CEL format.
265        *
266        * @param availabilityCondition the {@code AvailabilityCondition} to set
267        * @return this {@code Builder} object
268        */
269       @CanIgnoreReturnValue
270       public Builder setAvailabilityCondition(AvailabilityCondition availabilityCondition) {
271         this.availabilityCondition = availabilityCondition;
272         return this;
273       }
274 
275       public AccessBoundaryRule build() {
276         return new AccessBoundaryRule(
277             availableResource, availablePermissions, availabilityCondition);
278       }
279     }
280 
281     /**
282      * An optional condition that can be used as part of a {@link AccessBoundaryRule} to further
283      * restrict permissions.
284      *
285      * <p>For example, you can define an AvailabilityCondition that applies to a set of Cloud
286      * Storage objects whose names start with auth:
287      *
288      * <pre><code>
289      * AvailabilityCondition availabilityCondition = AvailabilityCondition.newBuilder()
290      *   .setExpression("resource.name.startsWith('projects/_/buckets/bucket-123/objects/auth')")
291      *   .build();
292      * </code></pre>
293      */
294     public static final class AvailabilityCondition {
295       private final String expression;
296 
297       @Nullable private final String title;
298       @Nullable private final String description;
299 
300       AvailabilityCondition(
301           String expression, @Nullable String title, @Nullable String description) {
302         this.expression = checkNotNull(expression);
303         this.title = title;
304         this.description = description;
305 
306         checkArgument(!expression.isEmpty(), "The provided expression is empty.");
307       }
308 
309       public String getExpression() {
310         return expression;
311       }
312 
313       @Nullable
314       public String getTitle() {
315         return title;
316       }
317 
318       @Nullable
319       public String getDescription() {
320         return description;
321       }
322 
323       public static Builder newBuilder() {
324         return new Builder();
325       }
326 
327       public static final class Builder {
328         private String expression;
329 
330         @Nullable private String title;
331         @Nullable private String description;
332 
333         private Builder() {}
334 
335         /**
336          * Sets the required expression which must be defined in Common Expression Language (CEL)
337          * format.
338          *
339          * <p>This expression specifies the Cloud Storage object where permissions are available.
340          * See <a href='https://cloud.google.com/iam/docs/conditions-overview#cel'>for more
341          * information.</a>
342          *
343          * @param expression the expression to set
344          * @return this {@code Builder} object
345          */
346         @CanIgnoreReturnValue
347         public Builder setExpression(String expression) {
348           this.expression = expression;
349           return this;
350         }
351 
352         /**
353          * Sets the optional title that identifies the purpose of the condition.
354          *
355          * @param title the title to set
356          * @return this {@code Builder} object
357          */
358         @CanIgnoreReturnValue
359         public Builder setTitle(String title) {
360           this.title = title;
361           return this;
362         }
363 
364         /**
365          * Sets the description that details the purpose of the condition.
366          *
367          * @param description the description to set
368          * @return this {@code Builder} object
369          */
370         @CanIgnoreReturnValue
371         public Builder setDescription(String description) {
372           this.description = description;
373           return this;
374         }
375 
376         public AvailabilityCondition build() {
377           return new AvailabilityCondition(expression, title, description);
378         }
379       }
380     }
381   }
382 }
383