AddIngressSecurityRuleTask.groovy
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2019-2022 Andres Almiray.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package org.kordamp.gradle.plugin.oci.tasks.instance
019 
020 import com.oracle.bmc.core.VirtualNetworkClient
021 import com.oracle.bmc.core.model.IngressSecurityRule
022 import com.oracle.bmc.core.model.PortRange
023 import com.oracle.bmc.core.model.SecurityList
024 import com.oracle.bmc.core.model.TcpOptions
025 import com.oracle.bmc.core.model.UdpOptions
026 import com.oracle.bmc.core.model.UpdateSecurityListDetails
027 import com.oracle.bmc.core.requests.GetSecurityListRequest
028 import com.oracle.bmc.core.requests.UpdateSecurityListRequest
029 import groovy.transform.CompileStatic
030 import org.gradle.api.provider.ListProperty
031 import org.gradle.api.provider.Property
032 import org.gradle.api.provider.Provider
033 import org.gradle.api.tasks.Input
034 import org.gradle.api.tasks.Internal
035 import org.gradle.api.tasks.Optional
036 import org.gradle.api.tasks.options.Option
037 import org.gradle.api.tasks.options.OptionValues
038 import org.kordamp.gradle.plugin.oci.tasks.AbstractOCITask
039 import org.kordamp.gradle.plugin.oci.tasks.interfaces.OCITask
040 import org.kordamp.gradle.plugin.oci.tasks.printers.SecurityListPrinter
041 import org.kordamp.gradle.plugin.oci.tasks.traits.SecurityListIdAwareTrait
042 import org.kordamp.jipsy.annotations.TypeProviderFor
043 
044 import java.util.regex.Matcher
045 import java.util.regex.Pattern
046 
047 import static org.kordamp.gradle.PropertyUtils.resolveValue
048 import static org.kordamp.gradle.util.StringUtils.isBlank
049 import static org.kordamp.gradle.util.StringUtils.isNotBlank
050 
051 /**
052  @author Andres Almiray
053  @since 0.1.0
054  */
055 @CompileStatic
056 @TypeProviderFor(OCITask)
057 class AddIngressSecurityRuleTask extends AbstractOCITask implements SecurityListIdAwareTrait {
058     static final String TASK_DESCRIPTION = 'Adds IngressSecurityRules to a SecurityList.'
059     static final Pattern PORT_RANGE_PATTERN = ~/(\d{1,5})\-(\d{1,5})/
060 
061     static enum PortType {
062         TCP, UDP
063     }
064 
065     @Internal
066     final Property<PortType> portType = project.objects.property(PortType)
067 
068     @Input
069     @Optional
070     final ListProperty<String> sourcePorts = project.objects.listProperty(String)
071 
072     @Input
073     @Optional
074     final ListProperty<String> destinationPorts = project.objects.listProperty(String)
075 
076     @Option(option = 'port-type', description = 'The port type to use. Defaults to TCP (OPTIONAL).')
077     void setPortType(PortType portType) {
078         getPortType().set(portType)
079     }
080 
081     @Input
082     @Optional
083     Provider<PortType> getResolvedPortType() {
084         project.providers.provider {
085             String value = resolveValue('OCI_PORT_TYPE', 'oci.port.type', this, project.name)
086             isNotBlank(value? PortType.valueOf(value: portType.getOrElse(PortType.TCP)
087         }
088     }
089 
090     @OptionValues("portType")
091     List<PortType> getAvailablePortTypes() {
092         return new ArrayList<PortType>(Arrays.asList(PortType.values()))
093     }
094 
095     @Option(option = 'source-port', description = 'The source port type to add. May be defined multiple times (REQUIRED).')
096     void setSourcePort(List<String> sourcePorts) {
097         for (String port : sourcePorts) {
098             Matcher m = PORT_RANGE_PATTERN.matcher(port)
099             if (m.matches()) {
100                 checkPort(m.group(1))
101                 checkPort(m.group(2))
102             else {
103                 checkPort(port)
104             }
105         }
106         this.sourcePorts.addAll(sourcePorts)
107     }
108 
109     void setSourcePort(String port) {
110         checkPort(port)
111         this.sourcePorts.add(port)
112     }
113 
114     @Option(option = 'destination-port', description = 'The destination port type to add. May be defined multiple times (REQUIRED).')
115     void setDestinationPort(List<String> destinationPorts) {
116         for (String port : destinationPorts) {
117             Matcher m = PORT_RANGE_PATTERN.matcher(port)
118             if (m.matches()) {
119                 checkPort(m.group(1))
120                 checkPort(m.group(2))
121             else {
122                 checkPort(port)
123             }
124         }
125         this.destinationPorts.addAll(destinationPorts)
126     }
127 
128     void setDestinationPort(String port) {
129         checkPort(port)
130         this.destinationPorts.add(port)
131     }
132 
133     private void checkPort(String port) {
134         try {
135             int p = port.toInteger()
136             if (p < || p > 65355) {
137                 throw new IllegalArgumentException("Port '$port' is out of range.")
138             }
139         catch (NumberFormatException e) {
140             throw new IllegalArgumentException("Port '$port' is not a valid integer")
141         }
142     }
143 
144     @Override
145     protected void doExecuteTask() {
146         validateSecurityListId()
147 
148         if (!getSourcePorts().present && !getDestinationPorts().present) {
149             throw new IllegalStateException("No ports have been defined in $path")
150         }
151 
152         VirtualNetworkClient client = createVirtualNetworkClient()
153 
154         SecurityList securityList = addIngressSecurityRules(this,
155             client,
156             getResolvedSecurityListId().get(),
157             getResolvedPortType().get(),
158             (List<String>getSourcePorts().getOrElse([]),
159             (List<String>getDestinationPorts().getOrElse([]))
160 
161         SecurityListPrinter.printSecurityList(this, securityList, 0)
162     }
163 
164     static SecurityList addIngressSecurityRules(OCITask owner,
165                                                 VirtualNetworkClient client,
166                                                 String securityListId,
167                                                 PortType portType,
168                                                 List<String> sourcePorts,
169                                                 List<String> destinationPorts) {
170         SecurityList securityList = client.getSecurityList(GetSecurityListRequest.builder()
171             .securityListId(securityListId)
172             .build())
173             .securityList
174 
175         List<IngressSecurityRule> rules = securityList.ingressSecurityRules
176         if (!destinationPorts) { // source ports only
177             for (String range : sourcePorts.sort(false)) {
178                 List<Integer> ports = extractRange(range)
179                 int min = ports[0]
180                 int max = ports[1]
181 
182                 IngressSecurityRule.Builder builder = createIngressSecurityRuleBuilder()
183 
184                 switch (portType) {
185                     case PortType.TCP:
186                         builder = builder.tcpOptions(TcpOptions.builder()
187                             .sourcePortRange(PortRange.builder()
188                                 .min(min)
189                                 .max(max)
190                                 .build())
191                             .build())
192                         break
193                     case PortType.UDP:
194                         builder = builder.udpOptions(UdpOptions.builder()
195                             .sourcePortRange(PortRange.builder()
196                                 .min(min)
197                                 .max(max)
198                                 .build())
199                             .build())
200                         break
201                     default:
202                         throw new IllegalStateException("Invalid port type '$portType'")
203                 }
204 
205                 IngressSecurityRule rule = builder.build()
206                 if (!rules.contains(rule)) rules << rule
207             }
208         else if (!sourcePorts) { // dest ports only
209             for (String range : destinationPorts.sort(false)) {
210                 List<Integer> ports = extractRange(range)
211                 int min = ports[0]
212                 int max = ports[1]
213 
214                 IngressSecurityRule.Builder builder = createIngressSecurityRuleBuilder()
215 
216                 switch (portType) {
217                     case PortType.TCP:
218                         builder = builder.tcpOptions(TcpOptions.builder()
219                             .destinationPortRange(PortRange.builder()
220                                 .min(min)
221                                 .max(max)
222                                 .build())
223                             .build())
224                         break
225                     case PortType.UDP:
226                         builder = builder.udpOptions(UdpOptions.builder()
227                             .destinationPortRange(PortRange.builder()
228                                 .min(min)
229                                 .max(max)
230                                 .build())
231                             .build())
232                         break
233                     default:
234                         throw new IllegalStateException("Invalid port type '$portType'")
235                 }
236 
237                 IngressSecurityRule rule = builder.build()
238                 if (!rules.contains(rule)) rules << rule
239             }
240         else // both
241             List<List<String>> combinations = GroovyCollections.combinations(sourcePorts, destinationPorts)
242             for (List<String> ranges : combinations) {
243                 List<Integer> ports = extractRange(ranges[0])
244                 int smin = ports[0]
245                 int smax = ports[1]
246                 ports = extractRange(ranges[1])
247                 int dmin = ports[0]
248                 int dmax = ports[1]
249 
250                 IngressSecurityRule.Builder builder = createIngressSecurityRuleBuilder()
251 
252                 switch (portType) {
253                     case PortType.TCP:
254                         builder = builder.tcpOptions(TcpOptions.builder()
255                             .sourcePortRange(PortRange.builder()
256                                 .min(smin)
257                                 .max(smax)
258                                 .build())
259                             .destinationPortRange(PortRange.builder()
260                                 .min(dmin)
261                                 .max(dmax)
262                                 .build())
263                             .build())
264                         break
265                     case PortType.UDP:
266                         builder = builder.udpOptions(UdpOptions.builder()
267                             .sourcePortRange(PortRange.builder()
268                                 .min(smin)
269                                 .max(smax)
270                                 .build())
271                             .destinationPortRange(PortRange.builder()
272                                 .min(dmin)
273                                 .max(dmax)
274                                 .build())
275                             .build())
276                         break
277                     default:
278                         throw new IllegalStateException("Invalid port type '$portType'")
279                 }
280 
281                 IngressSecurityRule rule = builder.build()
282                 if (!rules.contains(rule)) rules << rule
283             }
284         }
285 
286         client.updateSecurityList(UpdateSecurityListRequest.builder()
287             .securityListId(securityListId)
288             .updateSecurityListDetails(UpdateSecurityListDetails.builder()
289                 .ingressSecurityRules(rules)
290                 .build())
291             .build())
292             .securityList
293     }
294 
295     private static IngressSecurityRule.Builder createIngressSecurityRuleBuilder() {
296         IngressSecurityRule.builder()
297             .source('0.0.0.0/0')
298             .sourceType(IngressSecurityRule.SourceType.CidrBlock)
299             .isStateless(false)
300             .protocol('6')
301     }
302 
303     private static List<Integer> extractRange(String range) {
304         int min, max = 0
305         Matcher m = PORT_RANGE_PATTERN.matcher(range)
306         if (m.matches()) {
307             min = Integer.parseInt(m.group(1))
308             max = Integer.parseInt(m.group(2))
309         else {
310             min = max = Integer.parseInt(range)
311         }
312         [min, max]
313     }
314 }