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 < 1 || 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 }
|