aboutsummaryrefslogtreecommitdiffstats
path: root/sidecar/rproxy
diff options
context:
space:
mode:
Diffstat (limited to 'sidecar/rproxy')
-rw-r--r--sidecar/rproxy/License.txt17
-rw-r--r--sidecar/rproxy/README.md96
-rw-r--r--sidecar/rproxy/config/auth/client-cert.p12bin0 -> 2556 bytes
-rw-r--r--sidecar/rproxy/config/auth/tomcat_keystorebin0 -> 3594 bytes
-rw-r--r--sidecar/rproxy/config/auth/uri-authorization.json114
-rw-r--r--sidecar/rproxy/config/cadi.properties19
-rw-r--r--sidecar/rproxy/config/forward-proxy.properties4
-rw-r--r--sidecar/rproxy/config/logback-spring.xml48
-rw-r--r--sidecar/rproxy/config/primary-service.properties5
-rw-r--r--sidecar/rproxy/config/readme.txt1
-rw-r--r--sidecar/rproxy/config/reverse-proxy.properties1
-rw-r--r--sidecar/rproxy/config/security/keyfile27
-rw-r--r--sidecar/rproxy/pom.xml219
-rw-r--r--sidecar/rproxy/src/main/bin/start.sh31
-rw-r--r--sidecar/rproxy/src/main/docker/Dockerfile36
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyApplication.java182
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyAuthorizationFilter.java211
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyService.java166
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/CadiProperties.java41
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ForwardProxyProperties.java67
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/PrimaryServiceProperties.java58
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxySSLProperties.java56
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxyURIAuthorizationProperties.java38
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyEntryExitLoggingAspect.java45
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTime.java51
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTimeAnnotation.java30
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/mocks/ReverseProxyMockCadiFilter.java143
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyAuthorization.java34
-rw-r--r--sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyUtils.java29
-rw-r--r--sidecar/rproxy/src/main/resources/application.properties22
-rw-r--r--sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/PermissionMatchingTest.java262
-rw-r--r--sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyApplicationTest.java158
-rw-r--r--sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyIT.java48
-rw-r--r--sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyTestConfig.java42
-rw-r--r--sidecar/rproxy/src/test/resources/forward-proxy.properties4
-rw-r--r--sidecar/rproxy/src/test/resources/primary-service.properties3
-rw-r--r--sidecar/rproxy/version.properties13
37 files changed, 2321 insertions, 0 deletions
diff --git a/sidecar/rproxy/License.txt b/sidecar/rproxy/License.txt
new file mode 100644
index 0000000..05117f8
--- /dev/null
+++ b/sidecar/rproxy/License.txt
@@ -0,0 +1,17 @@
+============LICENSE_START=======================================================
+org.onap.aaf
+================================================================================
+Copyright © 2018 European Software Marketing Ltd.
+================================================================================
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+============LICENSE_END========================================================= \ No newline at end of file
diff --git a/sidecar/rproxy/README.md b/sidecar/rproxy/README.md
new file mode 100644
index 0000000..c8c22fe
--- /dev/null
+++ b/sidecar/rproxy/README.md
@@ -0,0 +1,96 @@
+# Introduction
+
+The AAF Reverse Proxy is a proxy microservice which intercepts incoming REST requests by, extracting the credentials from the request and authenticate/authorises
+with a configured security provider. It is one of two components (along with the Forward proxy) deployed as a Kubernetes sidecar to
+separate the responsibility of authentication and authorization away from the primary microservice, this service is responsible for
+controlling access to the REST URL endpoints exposed by the primary microservice, and propogating security credentials to downstream microservices.
+
+## Features
+
+Reverse Proxy:
+
+* The service will intercept all incoming REST requests to the primary service, extract and cache the token credentials in the Forward proxy.
+* Invokes the authentication and authorisation providers to validate the extracted tokens, and retrieve its list of authorisations
+* Invokes the enforcement point filter to determine whether the incoming request URI and retrieved permissions match the list of granted URIs and permissions
+ configured in the URI authorisation file. If authorisation is successful, forward the request to the primary service.
+
+## Configuring the rProxy service
+The rProxy service is configured through property and json files that resides under the ${CONFIG_HOME} environment variable.
+
+The files have the following configurable properties:
+
+###cadi.properties
+
+cadi_loglevel log level of the cadi filter, e.g. DEBUG, INFO
+cadi_keyfile location to the cadi key file
+cadi_truststore
+cadi_truststore_password
+aaf_url hostname and port of the server hosting the AAF service, e.g. https://aaf.osaaf.org:30247
+aaf_env AAF environment type, e.g. DEV, PROD
+aaf_id aafadmin user, e.g. demo@people.osaaf.org
+aaf_password aafadmin user password encrypted with the cadi_keyfile, e.g. enc:92w4px0y_rrm265LXLpw58QnNPgDXykyA1YTrflbAKz
+cadi_x509_issuers colon separated list of client cert issuers
+
+###reverse-proxy.properties
+
+transactionid.header.name This is the name of the header in incoming requests that will contain the transaction ID. X-TransactionId
+
+###primary-service.properties
+
+primary-service.protocol http protocol of the primary service e.g. https
+primary-service.host location of the primary service, since this sidecar resides in the same pod of the primary service. localhost
+primary-service.port port of the primary service
+
+###forward-proxy.properties
+
+forward-proxy.protocol http protocol of the fproxy service e.g. https
+forward-proxy.host location of the fproxy service, since this sidecar resides in the same pod of the primary service. localhost
+forward-proxy.port port of the fproxy service
+forward-proxy.cacheurl URI to the store credential cache. /credential-cache
+
+### auth/uri-authorization.json
+This file contains the list of required AAF permissions authorised for the request URI, permissions will be tested against the first matching URI.
+If the user doesn't have those permissions then the next matching URI will be tested until the list of URIs is exhausted.
+URIs will be matched in order as positioned in the configuration file. Wildcarding is supported as standard regular expression matches for both URIs and permissions.
+
+[
+ {
+ "uri": "URI 1",
+ "permissions": [
+ "permission 1",
+ "permission 2",
+ "..."]
+ },
+ {
+ "uri": "URI 2",
+ "permissions": [
+ "permission 3",
+ "permission 4",
+ "..."]
+ }
+]
+
+e.g.
+[
+ {
+ "uri": "\/aai\/v13\/cloud-infrastructure\/cloud-regions$",
+ "permissions": [
+ "org.onap.osaaf.resources.access|rest|read"
+ ]
+ },
+ {
+ "uri": "\/aai\/v13\/cloud-infrastructure\/cloud-regions\/cloud-region\/[^\/]+[\/][^\/]+$*",
+ "permissions": [
+ "org.onap.osaaf.resources.access|clouds|read",
+ "org.onap.osaaf.auth.resources.access|tenants|read"
+ ]
+ },
+ {
+ "uri": "\/aai\/v13\/cloud-infrastructure\/cloud-regions\/cloud-region\/[^\/]+[\/][^\/]+\/tenants/tenant/[^\/]+/vservers/vserver/[^\/]+$",
+ "permissions": [
+ "org.onap.osaaf.auth.resources.access|clouds|read",
+ "org.onap.osaaf.auth.resources.access|tenants|read",
+ "org.onap.osaaf.auth.resources.access|vservers|read"
+ ]
+ }
+] \ No newline at end of file
diff --git a/sidecar/rproxy/config/auth/client-cert.p12 b/sidecar/rproxy/config/auth/client-cert.p12
new file mode 100644
index 0000000..dbf4fca
--- /dev/null
+++ b/sidecar/rproxy/config/auth/client-cert.p12
Binary files differ
diff --git a/sidecar/rproxy/config/auth/tomcat_keystore b/sidecar/rproxy/config/auth/tomcat_keystore
new file mode 100644
index 0000000..99129c1
--- /dev/null
+++ b/sidecar/rproxy/config/auth/tomcat_keystore
Binary files differ
diff --git a/sidecar/rproxy/config/auth/uri-authorization.json b/sidecar/rproxy/config/auth/uri-authorization.json
new file mode 100644
index 0000000..29b152d
--- /dev/null
+++ b/sidecar/rproxy/config/auth/uri-authorization.json
@@ -0,0 +1,114 @@
+ [
+ {
+ "uri": "\/not\/allowed\/at\/all$",
+ "permissions": [
+ "test.auth.access\\|not\\|granted"
+ ]
+ },
+ {
+ "uri": "\/single\/permission\/required$",
+ "permissions": [
+ "test.single.access\\|single\\|permission"
+ ]
+ },
+ {
+ "uri": "\/multiple\/permissions\/required$",
+ "permissions": [
+ "test.multiple.access\\|first\\|permission",
+ "test.multiple.access\\|second\\|permission",
+ "test.multiple.access\\|third\\|permission"
+ ]
+ },
+ {
+ "uri": "\/multiple\/permissions\/required/one/missing$",
+ "permissions": [
+ "test.multiple.access\\|first\\|permission",
+ "test.multiple.access\\|second\\|permission",
+ "test.multiple.access\\|third\\|permission",
+ "test.multiple.access\\|fourth\\|permission"
+ ]
+ },
+ {
+ "uri": "\/wildcard\/permission\/granted$",
+ "permissions": [
+ "test.wildcard.access\\|first\\|permission",
+ "test.wildcard.access\\|second\\|permission",
+ "test.wildcard.access\\|third\\|consent"
+ ]
+ },
+ {
+ "uri": "\/instance\/wildcard\/permission\/granted$",
+ "permissions": [
+ "test.wildcard.access\\|first\\|permission",
+ "test.wildcard.access\\|second\\|permission",
+ "test.wildcard.access\\|third\\|permission"
+ ]
+ },
+ {
+ "uri": "\/action\/wildcard\/permission\/granted$",
+ "permissions": [
+ "test.wildcard.access\\|first\\|permission",
+ "test.wildcard.access\\|first\\|permission",
+ "test.wildcard.access\\|first\\|consent"
+ ]
+ },
+ {
+ "uri": "\/services\/getAAFRequest$",
+ "permissions": [
+ "test.auth.access\\|services\\|GET,PUT"
+ ]
+ },
+ {
+ "uri": "\/admin\/getAAFRequest$",
+ "permissions": [
+ "test.auth.access\\|admin\\|GET,PUT,POST"
+ ]
+ },
+ {
+ "uri": "\/service\/aai\/webapp\/index.html$",
+ "permissions": [
+ "test.auth.access\\|services\\|GET,PUT"
+ ]
+ },
+ {
+ "uri": "\/services\/aai\/webapp\/index.html$",
+ "permissions": [
+ "test.auth.access\\|services\\|GET,PUT"
+ ]
+ },
+ {
+ "uri": "\/$",
+ "permissions": [
+ "\\|services\\|GET",
+ "test\\.auth\\.access\\|services\\|GET,PUT"
+ ]
+ },
+ {
+ "uri": "\/aai\/v13\/cloud-infrastructure\/cloud-regions$",
+ "permissions": [
+ "test\\.auth\\.access\\|rest\\|write",
+ "test\\.auth\\.access\\|rpc\\|write"
+ ]
+ },
+ {
+ "uri": "\/aai\/v13\/cloud-infrastructure\/cloud-regions\/cloud-region\/[^\/]+[\/][^\/]+$*",
+ "permissions": [
+ "test.auth.access\\|clouds\\|read",
+ "test.auth.access\\|tenants\\|read"
+ ]
+ },
+ {
+ "uri": "\/aai\/v13\/cloud-infrastructure\/cloud-regions\/cloud-region\/[^\/]+[\/][^\/]+\/tenants/tenant/[^\/]+/vservers/vserver/[^\/]+$",
+ "permissions": [
+ "test.auth.access\\|clouds\\|read",
+ "test.auth.access\\|tenants\\|read",
+ "test.auth.access\\|vservers\\|read"
+ ]
+ },
+ {
+ "uri": "\/rproxy\/.*",
+ "permissions": [
+ "org\\.access\\|rproxy\\|get"
+ ]
+ }
+ ] \ No newline at end of file
diff --git a/sidecar/rproxy/config/cadi.properties b/sidecar/rproxy/config/cadi.properties
new file mode 100644
index 0000000..543bc1e
--- /dev/null
+++ b/sidecar/rproxy/config/cadi.properties
@@ -0,0 +1,19 @@
+# This is a normal Java Properties File
+# Comments are with Pound Signs at beginning of lines,
+# and multi-line expression of properties can be obtained by backslash at end of line
+
+cadi_loglevel=DEBUG
+cadi_keyfile=config/security/keyfile
+
+cadi_truststore=config/auth/tomcat_keystore
+cadi_truststore_password=OBF:1y0q1uvc1uum1uvg1pil1pjl1uuq1uvk1uuu1y10
+
+# Configure AAF
+aaf_url=https://aaf.osaaf.org:30247
+aaf_env=DEV
+
+aaf_id=demo@people.osaaf.org
+aaf_password=enc:92w4px0y_rrm265LXLpw58QnNPgDXykyA1YTrflbAKz
+
+# This is a colon separated list of client cert issuers
+cadi_x509_issuers=CN=ONAP, OU=ONAP, O=ONAP, L=Ottawa, ST=Ontario, C=CA \ No newline at end of file
diff --git a/sidecar/rproxy/config/forward-proxy.properties b/sidecar/rproxy/config/forward-proxy.properties
new file mode 100644
index 0000000..1b58d42
--- /dev/null
+++ b/sidecar/rproxy/config/forward-proxy.properties
@@ -0,0 +1,4 @@
+forward-proxy.protocol = https
+forward-proxy.host = localhost
+forward-proxy.port = 10680
+forward-proxy.cacheurl = /credential-cache \ No newline at end of file
diff --git a/sidecar/rproxy/config/logback-spring.xml b/sidecar/rproxy/config/logback-spring.xml
new file mode 100644
index 0000000..5b3a8dc
--- /dev/null
+++ b/sidecar/rproxy/config/logback-spring.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+ <property name="LOGS" value="./logs/reverse-proxy" />
+ <property name="FILEPREFIX" value="application" />
+
+ <appender name="Console"
+ class="ch.qos.logback.core.ConsoleAppender">
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <Pattern>
+ %d{ISO8601} %-5level [%t] %C{1.}: %msg%n%throwable
+ </Pattern>
+ </layout>
+ </appender>
+
+ <appender name="RollingFile"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${LOGS}/${FILEPREFIX}.log</file>
+ <encoder
+ class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ </encoder>
+
+ <rollingPolicy
+ class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- rollover daily and when the file reaches 10 MegaBytes -->
+ <fileNamePattern>${LOGS}/archived/${FILEPREFIX}-%d{yyyy-MM-dd}.%i.log
+ </fileNamePattern>
+ <timeBasedFileNamingAndTriggeringPolicy
+ class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+ <maxFileSize>10MB</maxFileSize>
+ </timeBasedFileNamingAndTriggeringPolicy>
+ </rollingPolicy>
+ </appender>
+
+ <!-- LOG everything at INFO level -->
+ <root level="info">
+ <appender-ref ref="RollingFile" />
+ <appender-ref ref="Console" />
+ </root>
+
+ <!-- LOG "org.onap.aaf.rproxy" at DEBUG level -->
+ <logger name="org.onap.aaf.rproxy" level="debug" additivity="false">
+ <appender-ref ref="RollingFile" />
+ <appender-ref ref="Console" />
+ </logger>
+
+</configuration>
diff --git a/sidecar/rproxy/config/primary-service.properties b/sidecar/rproxy/config/primary-service.properties
new file mode 100644
index 0000000..d0f7a32
--- /dev/null
+++ b/sidecar/rproxy/config/primary-service.properties
@@ -0,0 +1,5 @@
+primary-service.protocol = https
+primary-service.host = localhost
+
+# This needs to be configured to match the port of the primary service running in the pod
+primary-service.port = 9000
diff --git a/sidecar/rproxy/config/readme.txt b/sidecar/rproxy/config/readme.txt
new file mode 100644
index 0000000..79cf29e
--- /dev/null
+++ b/sidecar/rproxy/config/readme.txt
@@ -0,0 +1 @@
+Relevant configuration files need to be copied here to successfully run this service locally. \ No newline at end of file
diff --git a/sidecar/rproxy/config/reverse-proxy.properties b/sidecar/rproxy/config/reverse-proxy.properties
new file mode 100644
index 0000000..8d46e1f
--- /dev/null
+++ b/sidecar/rproxy/config/reverse-proxy.properties
@@ -0,0 +1 @@
+transactionid.header.name=X-TransactionId \ No newline at end of file
diff --git a/sidecar/rproxy/config/security/keyfile b/sidecar/rproxy/config/security/keyfile
new file mode 100644
index 0000000..6cd12fc
--- /dev/null
+++ b/sidecar/rproxy/config/security/keyfile
@@ -0,0 +1,27 @@
+bZNOXiGDJ2_eiKBKWYLIFx27URvb-SWfmOl2d-QKetcVKIupOrsG-ScS_VXOtKN3Yxfb2cR6t7oM
+1RNpDnhsKAxDLM6A62IkS_h_Rp3Q9c2JeyomVmyiuHR7a2ARbelaMrX8WDrxXI_t9ce4pIHDVE29
+xiQm3Bdp7d7IiKkgg-ipvOU7Y6NEzeQbvHlHvRTJ3ZZMSwHxBOA5M8DhKN-AF1sqwozEVaNAuJxK
+BVdh72A6KTW7ieb_GvVQQp8h32BuOz8oJhZV7KaGXsWTEvXg9ImboY0h7Sl9hufgn1ZtDK1jxzGm
+6O6LBg1qezzZaFGTXRmHvaeYmEeYSu0bGsU4x-JCU0RyhNTzFhkhjNoccaqPXBdcJymLf096mD99
+QLS8nyji_KtLQJL1fqr500c8p6SOURLPgG6Gzkn4ghgFYlfgve92xs1R3ggHKhNTLV4HJ4O6iSDm
+zCoHeRbsZR1JER9yxT-v8NtcHOMAZe1oDQeY6jVyxb-bhaonN6eZPI4nyF6MHJQtWKhGARC_kOs6
+x9E0ZdAEp5TrX7F7J5PwkXzbCOuSiTVftOBum43iUB4q9He8tn2tJ0X4LtLHT3bPl16wWnZm9RPf
+8wBtTJh4QP_cTStPq1ftSaLIAuqVFpbiC2DxGemXZn3QvykuYqa-rKeYPoIJ5dtWd5rNb_hhcSIz
+FakKTELb0HWYGji98TBF6PaStea2f2m-wGX_uQGD7_Dijl6AgnV9koKVs1bN1XljLtNMPbLdD8sz
+UCvc5lwvCFyyeunljI7os1fgwBmaMyckflq5VfZv9kFxom6jFLbcozylQ_uBg4j7oCP79IXVUI-r
+banZltOSmm8zHGc2R9UlUyxJWBi01yxwi1hUtn9g1H4RtncQpu3BY0Qvu5YLAmS5imivUnGVZWbv
+6wcqnJt5HwaVatE9NHONSLNTViQPsUOutWZBZxhJtAncdZuWOYZSh4TPzUJWvt6zT0E3YMBc_UuG
+yPmdLyqo7qGHR8YWRqq_vq6ISJqENMnVD6X9-BeI6KM4GPEAlDWyhgENXxQFjG45ufg3UpP8LBTB
+xDntlfkphRumsd13-8IlvwVtlpgnbuCMbwP_-lNVeNJcdA1InPt79oY-SEVZ-RVM1881ZASCnFeB
+lh3BTc_bGQ8YoC9s6iHtcCK_1SdbwzBfQBJUqqcYsa8hJLe-j8di7KCaFzI3a-UXWKuuWljpbKbq
+ibd48UFJt_34_GxkD6bmLxycuNH-og2Sd2VcYU0o5UarcrY4-2sgFPE7Mzxovrl98uayfgNF9DqE
+fJ4MwFGqLRtEHlm4zfuMxQ5Rh_giMUHDJApc1DYRkxdGbNUd4bC4aRBln2IhN-rNKbSVtiW_uT6v
+1KTMGmElvktjPWybJd2SvhT5qOLUM81-cmZzAsNa04jxZLBlQn_1fel3IroVos4Ohbdhar2NG6T5
+liten9RZ9P4Cg9RWhgeQonAD5kqLWXAHnCfffb5CVcAU5PHqkCgCbdThvD0-zIGETLO9AE0jKISc
+0o67CUZn3MzJ9pP_3gh-ALr2w-KAwqasqCf0igf1wmEDijv9wEDcgDm39ERIElTpGKgfyuVl4F8u
+PrpK5ZfpUYySUB6CZFQVVz0MvH6E7orQk4dCKFIimV_XwEtGijBttrTvyV6xYNScAEw_olt-0mdm
+8UEKSsuqSyDMxUWLjKJT19rNedahYJNtI87WR9Fhhjsrai9Or3a-srOYa56wcvSj2ZHbkevbO9Xv
+dQ2wzWCGEAMQSpSr83n0XEpR2pZT19Z19Svbhr08mnt2JNykCk60FLCeDTUOylJtYw6YOjqBizQZ
+-85B51BCbSEaAKJkgT9-8n_-LGW5aPBrBB_9FT7UIYczNEt3B1Lqr2s4ipPI_36JecEfqaS2cNLn
+c0ObAtNGAONkhO5LYLneMR3fZPMFuOX1-rMObPgE0i9dYqWDZ_30w9rpRsmiWyxYi5lvWDxU5L1J
+uJxwREz3oa_VgpSC3Y2oxCufdQwzBk57iVLDOb1qs_Hwj1SWd1nukWyAo2-g5sR1folAEcao \ No newline at end of file
diff --git a/sidecar/rproxy/pom.xml b/sidecar/rproxy/pom.xml
new file mode 100644
index 0000000..530a459
--- /dev/null
+++ b/sidecar/rproxy/pom.xml
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ ============LICENSE_START=======================================================
+ org.onap.aaf
+ ================================================================================
+ Copyright © 2018 European Software Marketing Ltd.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.aaf.cadi</groupId>
+ <artifactId>sidecar</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>rproxy</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>aaf-rproxy</name>
+ <description>ONAP AAF Reverse Proxy Microservice For Pluggable Security</description>
+
+ <properties>
+ <spring.boot.version>2.0.3.RELEASE</spring.boot.version>
+ <docker.location>${basedir}/target</docker.location>
+ <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <!-- Import dependency management from Spring Boot -->
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>${spring.boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+
+ <!-- Spring Boot Dependencies -->
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jetty</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-aop</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Application Dependencies -->
+
+ <dependency>
+ <groupId>org.onap.aaf.cadi.sidecar</groupId>
+ <artifactId>fproxy</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.8.5</version><!--$NO-MVN-MAN-VER$-->
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ </dependency>
+
+ <!-- CADI -->
+
+ <dependency>
+ <groupId>org.onap.aaf.authz</groupId>
+ <artifactId>aaf-cadi-aaf</artifactId>
+ <version>2.1.2-SNAPSHOT</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <finalName>rproxy</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring.boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ <configuration>
+ <classifier>exec</classifier>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <reuseForks>false</reuseForks>
+ <forkCount>1</forkCount>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.0.2</version>
+ <executions>
+ <execution>
+ <id>copy-docker-file</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>target</outputDirectory>
+ <overwrite>true</overwrite>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/docker</directory>
+ <filtering>true</filtering>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/bin/</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>0.4.11</version>
+ <dependencies>
+ <dependency>
+ <groupId>com.github.jnr</groupId>
+ <artifactId>jnr-unixsocket</artifactId>
+ <version>0.13</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <verbose>true</verbose>
+ <serverId>docker-hub</serverId>
+ <imageName>${docker.push.registry}/onap/${project.artifactId}</imageName>
+ <dockerDirectory>${docker.location}</dockerDirectory>
+ <imageTags>
+ <imageTag>latest</imageTag>
+ </imageTags>
+ <forceTags>true</forceTags>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sidecar/rproxy/src/main/bin/start.sh b/sidecar/rproxy/src/main/bin/start.sh
new file mode 100644
index 0000000..4330af5
--- /dev/null
+++ b/sidecar/rproxy/src/main/bin/start.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# ============LICENSE_START=======================================================
+# org.onap.aaf
+# ================================================================================
+# Copyright © 2018 European Software Marketing Ltd.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+
+BASEDIR="/opt/app/rproxy"
+
+if [ -z "${KEY_STORE_PASSWORD}" ]; then
+ echo "KEY_STORE_PASSWORD must be set in order to start up process"
+ exit 1
+fi
+
+PROPS="-DKEY_STORE_PASSWORD=${KEY_STORE_PASSWORD}"
+JVM_MAX_HEAP=${MAX_HEAP:-1024}
+
+exec java -Xmx${JVM_MAX_HEAP}m ${PROPS} -jar ${BASEDIR}/rproxy-exec.jar
diff --git a/sidecar/rproxy/src/main/docker/Dockerfile b/sidecar/rproxy/src/main/docker/Dockerfile
new file mode 100644
index 0000000..56b32fa
--- /dev/null
+++ b/sidecar/rproxy/src/main/docker/Dockerfile
@@ -0,0 +1,36 @@
+FROM ubuntu:14.04
+
+ARG MICRO_HOME=/opt/app/rproxy
+ARG BIN_HOME=$MICRO_HOME/bin
+ARG JAR_FILE=rproxy-exec.jar
+
+RUN apt-get update
+
+# Install and setup java8
+RUN apt-get update && apt-get install -y software-properties-common
+## sudo -E is required to preserve the environment. If you remove that line, it will most like freeze at this step
+RUN sudo -E add-apt-repository ppa:openjdk-r/ppa && apt-get update && apt-get install -y openjdk-8-jdk
+
+RUN sudo dpkg --purge --force-depends ca-certificates-java
+RUN sudo apt-get install ca-certificates-java
+
+## Setup JAVA_HOME, this is useful for docker commandline
+ENV JAVA_HOME usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)
+RUN export JAVA_HOME
+
+# Build up the deployment folder structure
+RUN mkdir -p $MICRO_HOME
+COPY ${JAR_FILE} $MICRO_HOME
+RUN mkdir -p $BIN_HOME
+COPY *.sh $BIN_HOME
+RUN chmod 755 $BIN_HOME/*
+RUN ln -s /logs $MICRO_HOME/logs
+RUN mkdir /logs
+# Create the appuser
+RUN groupadd -r appgroup && \
+ useradd -r -u 1001 -g appgroup appuser && \
+ chown -R appuser:appgroup $MICRO_HOME && \
+ chmod 777 /logs
+USER appuser
+
+CMD ["/opt/app/rproxy/bin/start.sh"]
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyApplication.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyApplication.java
new file mode 100644
index 0000000..ebceceb
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyApplication.java
@@ -0,0 +1,182 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Properties;
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.net.ssl.SSLContext;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.eclipse.jetty.util.security.Password;
+import org.onap.aaf.cadi.filter.CadiFilter;
+import org.onap.aaf.rproxy.config.ForwardProxyProperties;
+import org.onap.aaf.rproxy.config.PrimaryServiceProperties;
+import org.onap.aaf.rproxy.config.ReverseProxySSLProperties;
+import org.onap.aaf.rproxy.mocks.ReverseProxyMockCadiFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.RegistrationBean;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootApplication
+@ServletComponentScan
+@EnableConfigurationProperties(ReverseProxySSLProperties.class)
+@PropertySource("file:${CONFIG_HOME}/reverse-proxy.properties")
+public class ReverseProxyApplication extends SpringBootServletInitializer {
+
+ private static final String CADI_TRUSTSTORE_PASS = "cadi_truststore_password";
+
+ @Autowired
+ private Environment env;
+
+ /**
+ * Spring Boot Initialisation.
+ *
+ * @param args main args
+ */
+ public static void main(String[] args) {
+ String keyStorePassword = System.getProperty("KEY_STORE_PASSWORD");
+ if (keyStorePassword == null || keyStorePassword.isEmpty()) {
+ throw new IllegalArgumentException("Env property KEY_STORE_PASSWORD not set");
+ }
+ HashMap<String, Object> props = new HashMap<>();
+ props.put("server.ssl.key-store-password", Password.deobfuscate(keyStorePassword));
+ new ReverseProxyApplication()
+ .configure(new SpringApplicationBuilder(ReverseProxyApplication.class).properties(props)).run(args);
+ }
+
+ /**
+ * Set required trust store system properties using values from application.properties
+ */
+ @PostConstruct
+ public void setSystemProperties() {
+ String keyStorePath = env.getProperty("server.ssl.key-store");
+ if (keyStorePath != null) {
+ String keyStorePassword = env.getProperty("server.ssl.key-store-password");
+
+ if (keyStorePassword != null) {
+ System.setProperty("javax.net.ssl.keyStore", keyStorePath);
+ System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
+ System.setProperty("javax.net.ssl.trustStore", keyStorePath);
+ System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword);
+ } else {
+ throw new IllegalArgumentException("Env property server.ssl.key-store-password not set");
+ }
+ }
+ }
+
+ @Resource
+ private ReverseProxySSLProperties reverseProxySSLProperties;
+
+ @Resource
+ Properties cadiProps;
+
+ @Bean(name = "ForwardProxyProperties")
+ public ForwardProxyProperties forwardProxyProperties() {
+ return new ForwardProxyProperties();
+ }
+
+ @Bean(name = "PrimaryServiceProperties")
+ public PrimaryServiceProperties primaryServiceProperties() {
+ return new PrimaryServiceProperties();
+ }
+
+ @Profile("secure")
+ @Bean
+ public RestTemplate restTemplate(RestTemplateBuilder builder) throws GeneralSecurityException, IOException {
+ return new RestTemplate(new HttpComponentsClientHttpRequestFactory(getClientBuilder().build()));
+ }
+
+ @Profile("noHostVerification")
+ @Bean
+ public RestTemplate restTemplateNoHostVerification(RestTemplateBuilder builder)
+ throws GeneralSecurityException, IOException {
+ return new RestTemplate(new HttpComponentsClientHttpRequestFactory(
+ getClientBuilder().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build()));
+ }
+
+ private HttpClientBuilder getClientBuilder() throws GeneralSecurityException, IOException {
+
+ SSLContext sslContext = SSLContextBuilder.create()
+ .loadKeyMaterial(ResourceUtils.getFile(reverseProxySSLProperties.getClientcert()),
+ reverseProxySSLProperties.getKeystorePassword().toCharArray(),
+ reverseProxySSLProperties.getKeystorePassword().toCharArray())
+ .loadTrustMaterial(ResourceUtils.getFile(reverseProxySSLProperties.getKeystore()),
+ reverseProxySSLProperties.getKeystorePassword().toCharArray())
+ .build();
+
+ return HttpClients.custom().setSSLContext(sslContext);
+ }
+
+ @Profile("cadi")
+ @Bean
+ public FilterRegistrationBean<CadiFilter> registerCADIFilter() {
+
+ FilterRegistrationBean<CadiFilter> filterRegistrationBean = new FilterRegistrationBean<>();
+
+ filterRegistrationBean.setFilter(new CadiFilter());
+ filterRegistrationBean.addUrlPatterns("/*");
+ filterRegistrationBean.setName("CADIFilter");
+ filterRegistrationBean.setOrder(RegistrationBean.HIGHEST_PRECEDENCE);
+
+ // Deobfuscate truststore password
+ String trustStorePassword = cadiProps.getProperty(CADI_TRUSTSTORE_PASS);
+ if (trustStorePassword != null) {
+ cadiProps.setProperty(CADI_TRUSTSTORE_PASS, Password.deobfuscate(trustStorePassword));
+ }
+
+ // Add filter init params
+ cadiProps.forEach((k, v) -> filterRegistrationBean.addInitParameter((String) k, (String) v));
+
+ return filterRegistrationBean;
+ }
+
+ @Profile("mockCadi")
+ @Bean
+ public FilterRegistrationBean<ReverseProxyMockCadiFilter> registerMockCADIFilter() {
+
+ FilterRegistrationBean<ReverseProxyMockCadiFilter> filterRegistrationBean = new FilterRegistrationBean<>();
+
+ filterRegistrationBean.setFilter(new ReverseProxyMockCadiFilter());
+ filterRegistrationBean.addUrlPatterns("/*");
+ filterRegistrationBean.setName("CADIFilter");
+ filterRegistrationBean.setOrder(RegistrationBean.HIGHEST_PRECEDENCE);
+
+ return filterRegistrationBean;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyAuthorizationFilter.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyAuthorizationFilter.java
new file mode 100644
index 0000000..6374c9d
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyAuthorizationFilter.java
@@ -0,0 +1,211 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Resource;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.onap.aaf.cadi.CadiWrap;
+import org.onap.aaf.cadi.Permission;
+import org.onap.aaf.rproxy.config.ReverseProxyURIAuthorizationProperties;
+import org.onap.aaf.rproxy.utils.ReverseProxyAuthorization;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+@Component
+@Order(1)
+@EnableConfigurationProperties(ReverseProxyURIAuthorizationProperties.class)
+public class ReverseProxyAuthorizationFilter implements Filter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReverseProxyAuthorizationFilter.class);
+
+ private ReverseProxyAuthorization[] reverseProxyAuthorizations = new ReverseProxyAuthorization[] {};
+
+ @Resource
+ private ReverseProxyURIAuthorizationProperties reverseProxyURIAuthorizationProperties;
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ // Read in the URI Authorisation configuration file
+ String authFilePath = reverseProxyURIAuthorizationProperties.getConfigurationFile();
+ if (authFilePath != null) {
+ try (InputStream inputStream =
+ new FileInputStream(new File(reverseProxyURIAuthorizationProperties.getConfigurationFile()));
+ JsonReader jsonReader = new JsonReader(new InputStreamReader(inputStream))) {
+ reverseProxyAuthorizations = new Gson().fromJson(jsonReader, ReverseProxyAuthorization[].class);
+ } catch (IOException e) {
+ throw new ServletException("Authorizations config file not found.", e);
+ }
+ }
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ CadiWrap cadiWrap = (CadiWrap) servletRequest;
+ Principal principal = cadiWrap.getUserPrincipal();
+ List<Permission> grantedPermissions = new ArrayList<>();
+ cadiWrap.getLur().fishAll(principal, grantedPermissions);
+
+ if (LOGGER.isDebugEnabled()) {
+ logNeededPermissions();
+ }
+
+ String requestPath;
+ try {
+ requestPath = new URI(((HttpServletRequest) servletRequest).getRequestURI()).getPath();
+ } catch (URISyntaxException e) {
+ throw new ServletException("Request URI not valid", e);
+ }
+
+ if (authorizeRequest(grantedPermissions, requestPath)) {
+ LOGGER.info("Authorized");
+ filterChain.doFilter(servletRequest, servletResponse);
+ } else {
+ LOGGER.info("Unauthorized");
+ ((HttpServletResponse) servletResponse).setStatus(HttpStatus.FORBIDDEN_403);
+ ((HttpServletResponse) servletResponse).setContentType("application/json");
+ ((HttpServletResponse) servletResponse).sendError(HttpStatus.FORBIDDEN_403,
+ "Sorry, the request is not allowed");
+ }
+ }
+
+ /**
+ * Check if the granted permissions for the request path matches the configured needed permissions.
+ *
+ * @param grantedPermissions The granted permissions for the request path
+ * @param requestPath The request path
+ * @return true if permissions match
+ */
+ private boolean authorizeRequest(List<Permission> grantedPermissions, String requestPath) {
+ boolean authorized = false;
+ for (ReverseProxyAuthorization reverseProxyAuthorization : reverseProxyAuthorizations) {
+ if (requestPath.matches(reverseProxyAuthorization.getUri())) {
+ LOGGER.debug("The URI:{} matches:{}", requestPath, reverseProxyAuthorization.getUri());
+ if (checkPermissionsMatch(grantedPermissions, reverseProxyAuthorization)) {
+ authorized = true;
+ break;
+ }
+ } else {
+ LOGGER.debug("The URI:{} doesn't match any in the configuration:{}", requestPath,
+ reverseProxyAuthorization.getUri());
+ }
+ }
+ return authorized;
+ }
+
+ /**
+ * Check all needed permissions match the granted permissions.
+ *
+ * @param grantedPermissions the granted permissions
+ * @param reverseProxyAuthorization the bean that contains the needed permissions
+ * @return true if all needed permissions match
+ */
+ private boolean checkPermissionsMatch(List<Permission> grantedPermissions,
+ ReverseProxyAuthorization reverseProxyAuthorization) {
+
+ boolean matchedAllPermissions = true;
+ for (String neededPermission : reverseProxyAuthorization.getPermissions()) {
+
+ // Check needed permission is granted
+ boolean matchedNeededPermission = false;
+ for (Permission grantedPermission : grantedPermissions) {
+ if (checkGrantedPermission(neededPermission, grantedPermission.getKey())) {
+ LOGGER.debug("Permission match found - needed permission:{}, granted permission:{}",
+ neededPermission, grantedPermission.getKey());
+ matchedNeededPermission = true;
+ break;
+ }
+ }
+ if (!matchedNeededPermission) {
+ matchedAllPermissions = false;
+ break;
+ }
+ }
+ return matchedAllPermissions;
+ }
+
+ /**
+ * Check whether an AAF style permission matches a needed permission. Wildcards (*) are supported.
+ *
+ * @param neededPermission, the needed permission
+ * @param grantedPermission, the granted permission
+ *
+ * @return true if the needed permission matches a granted permission
+ */
+ private boolean checkGrantedPermission(String neededPermission, String grantedPermission) {
+ boolean permissionMatch = false;
+ if (grantedPermission.matches(neededPermission)) {
+ permissionMatch = true;
+ } else if (grantedPermission.contains("*")) {
+ String[] splitNeededPermission = neededPermission.split("\\\\\\|");
+ String[] splitGrantedPermission = grantedPermission.split("\\|");
+ if ((splitGrantedPermission[0].matches(splitNeededPermission[0]))
+ && (splitGrantedPermission[1].equals("*")
+ || splitGrantedPermission[1].matches(splitNeededPermission[1]))
+ && (splitGrantedPermission[2].equals("*")
+ || splitGrantedPermission[2].matches(splitNeededPermission[2]))) {
+ permissionMatch = true;
+ }
+ }
+ return permissionMatch;
+ }
+
+ /**
+ * Log the needed permissions for each URL configured.
+ */
+ private void logNeededPermissions() {
+ for (ReverseProxyAuthorization reverseProxyAuthorization : reverseProxyAuthorizations) {
+ LOGGER.debug("URI For authorization: {}", reverseProxyAuthorization.getUri());
+ for (String permission : reverseProxyAuthorization.getPermissions()) {
+ LOGGER.debug("\t Needed permission:{}", permission);
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ // No op
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyService.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyService.java
new file mode 100644
index 0000000..b5c000c
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/ReverseProxyService.java
@@ -0,0 +1,166 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Enumeration;
+import java.util.UUID;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import org.onap.aaf.fproxy.data.CredentialCacheData;
+import org.onap.aaf.fproxy.data.CredentialCacheData.CredentialType;
+import org.onap.aaf.rproxy.config.ForwardProxyProperties;
+import org.onap.aaf.rproxy.config.PrimaryServiceProperties;
+import org.onap.aaf.rproxy.logging.ReverseProxyMethodLogTimeAnnotation;
+import org.onap.aaf.rproxy.utils.ReverseProxyUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+@RestController
+@EnableConfigurationProperties({ForwardProxyProperties.class, PrimaryServiceProperties.class})
+public class ReverseProxyService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReverseProxyService.class);
+
+ private String validatedTransactionId;
+
+ @Resource(name = "ForwardProxyProperties")
+ private ForwardProxyProperties forwardProxyProperties;
+
+ @Resource(name = "PrimaryServiceProperties")
+ private PrimaryServiceProperties primaryServiceProperties;
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Value("${transactionid.header.name}")
+ private String transactionIdHeader;
+
+ @RequestMapping("/**")
+ @ReverseProxyMethodLogTimeAnnotation
+ public ResponseEntity<String> handleRequest(HttpServletRequest request,
+ @RequestHeader(value = "${transactionid.header.name}", defaultValue = "") String transactionId,
+ @RequestBody(required = false) String requestBody, HttpMethod requestMethod) throws URISyntaxException {
+ validatedTransactionId = getValidTransactionId(transactionId);
+
+ // Extract Request Permissions and store in Forward Proxy cache
+ CredentialCacheData credentialCacheData = getCredentialDataFromRequest(request);
+ if (credentialCacheData != null) {
+ postCredentialsToCache(credentialCacheData);
+ }
+
+ // Call out to Primary Service & Return Response
+ URI requestURI = new URI(request.getRequestURI());
+
+ LOGGER.debug("Request URI: {}", request.getRequestURI());
+
+ // Get Request Endpoint & substitute in local values
+ URI primaryServiceURI = new URI(primaryServiceProperties.getProtocol(), requestURI.getUserInfo(),
+ primaryServiceProperties.getHost(), Integer.parseInt(primaryServiceProperties.getPort()),
+ requestURI.getPath(), requestURI.getQuery(), requestURI.getFragment());
+
+ LOGGER.debug("Primary Service URI:{}, HTTP Method: {}", primaryServiceURI, requestMethod);
+
+ HttpHeaders requestHeaders = setForwardedRequestHeaders(request);
+ HttpEntity<String> httpEntity = new HttpEntity<>(requestBody, requestHeaders);
+
+ return restTemplate.exchange(primaryServiceURI, requestMethod, httpEntity, String.class);
+ }
+
+ private String getValidTransactionId(String transactionId) {
+ LOGGER.debug("Request transaction ID: {}", transactionId);
+ if (transactionId == null || !ReverseProxyUtils.validTransactionId(transactionId)) {
+ transactionId = UUID.randomUUID().toString();
+ }
+ LOGGER.debug("Validated transaction ID: {}", transactionId);
+ return transactionId;
+ }
+
+ private HttpHeaders setForwardedRequestHeaders(HttpServletRequest httpServletRequest) {
+ HttpHeaders httpHeaders = new HttpHeaders();
+ Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+ if (!headerName.equals(transactionIdHeader)) {
+ httpHeaders.set(headerName, httpServletRequest.getHeader(headerName));
+ }
+ }
+ // Always set transaction ID
+ httpHeaders.set(transactionIdHeader, validatedTransactionId);
+
+ return httpHeaders;
+ }
+
+ /**
+ * Retrieves credential data from request.
+ *
+ * @param request The request to retrieve credentials from
+ * @return The retrieved credential data, or null if no credentials are found in request
+ */
+ private CredentialCacheData getCredentialDataFromRequest(HttpServletRequest request) {
+ CredentialCacheData credentialCacheData = null;
+ String authValue = request.getHeader(HttpHeaders.AUTHORIZATION);
+ if (authValue != null) {
+ credentialCacheData = new CredentialCacheData(HttpHeaders.AUTHORIZATION, authValue, CredentialType.HEADER);
+ }
+ return credentialCacheData;
+ }
+
+ /**
+ * Posts credential data to credential cache endpoint
+ *
+ * @param credentialCacheData The credential data to post
+ * @throws URISyntaxException
+ */
+ private void postCredentialsToCache(CredentialCacheData credentialCacheData) throws URISyntaxException {
+ URI forwardProxyURI = new URI(forwardProxyProperties.getProtocol(), null, forwardProxyProperties.getHost(),
+ forwardProxyProperties.getPort(), forwardProxyProperties.getCacheurl() + "/" + validatedTransactionId,
+ null, null);
+
+ ResponseEntity<String> response =
+ restTemplate.postForEntity(forwardProxyURI, credentialCacheData, String.class);
+
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ throw new InvalidEndpointRequestException("Error posting to credential cache.",
+ "Status code: " + response.getStatusCodeValue() + " Message: " + response.getBody());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getName() + ": Forward proxy host:" + forwardProxyProperties.getHost()
+ + ": Primary service host:" + primaryServiceProperties.getHost();
+
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/CadiProperties.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/CadiProperties.java
new file mode 100644
index 0000000..0afb506
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/CadiProperties.java
@@ -0,0 +1,41 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.config;
+
+import java.net.MalformedURLException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.PropertiesFactoryBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.UrlResource;
+
+@Configuration
+public class CadiProperties {
+
+ @Value("${CONFIG_HOME}")
+ private String configHome;
+
+ @Bean(name = "cadiProps")
+ public PropertiesFactoryBean mapper() throws MalformedURLException {
+ PropertiesFactoryBean bean = new PropertiesFactoryBean();
+ bean.setLocation(new UrlResource("file:" + configHome + "/cadi.properties"));
+ return bean;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ForwardProxyProperties.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ForwardProxyProperties.java
new file mode 100644
index 0000000..3607797
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ForwardProxyProperties.java
@@ -0,0 +1,67 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration
+@PropertySource("file:${CONFIG_HOME}/forward-proxy.properties")
+@ConfigurationProperties(prefix = "forward-proxy")
+public class ForwardProxyProperties {
+
+ private String protocol;
+ private String host;
+ private int port;
+ private String cacheurl;
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getCacheurl() {
+ return cacheurl;
+ }
+
+ public void setCacheurl(String cacheurl) {
+ this.cacheurl = cacheurl;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/PrimaryServiceProperties.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/PrimaryServiceProperties.java
new file mode 100644
index 0000000..994dc55
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/PrimaryServiceProperties.java
@@ -0,0 +1,58 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration
+@PropertySource("file:${CONFIG_HOME}/primary-service.properties")
+@ConfigurationProperties(prefix = "primary-service")
+public class PrimaryServiceProperties {
+
+ private String protocol;
+ private String host;
+ private String port;
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getPort() {
+ return port;
+ }
+
+ public void setPort(String port) {
+ this.port = port;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxySSLProperties.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxySSLProperties.java
new file mode 100644
index 0000000..31e8a0d
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxySSLProperties.java
@@ -0,0 +1,56 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "server.ssl")
+public class ReverseProxySSLProperties {
+
+ private String keystore;
+ private String keystorePassword;
+ private String clientcert;
+
+ public String getKeystore() {
+ return keystore;
+ }
+
+ public void setKeystore(String keystore) {
+ this.keystore = keystore;
+ }
+
+ public String getClientcert() {
+ return clientcert;
+ }
+
+ public void setClientcert(String clientcert) {
+ this.clientcert = clientcert;
+ }
+
+ public String getKeystorePassword() {
+ return keystorePassword;
+ }
+
+ public void setKeystorePassword(String keystorePassword) {
+ this.keystorePassword = keystorePassword;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxyURIAuthorizationProperties.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxyURIAuthorizationProperties.java
new file mode 100644
index 0000000..da25d9c
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/config/ReverseProxyURIAuthorizationProperties.java
@@ -0,0 +1,38 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "uri.authorization")
+public class ReverseProxyURIAuthorizationProperties {
+
+ private String configurationFile;
+
+ public String getConfigurationFile() {
+ return configurationFile;
+ }
+
+ public void setConfigurationFile(String configurationFile) {
+ this.configurationFile = configurationFile;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyEntryExitLoggingAspect.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyEntryExitLoggingAspect.java
new file mode 100644
index 0000000..174420c
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyEntryExitLoggingAspect.java
@@ -0,0 +1,45 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.logging;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class ReverseProxyEntryExitLoggingAspect {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReverseProxyEntryExitLoggingAspect.class);
+
+ @Before("execution(* org.onap.platform.security.*.*(..))")
+ public void before(JoinPoint joinPoint) {
+ LOGGER.info("Entry of {}#{}", joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
+ }
+
+ @After("execution(* org.onap.platform.security.*.*(..))")
+ public void after(JoinPoint joinPoint) {
+ LOGGER.info("Exit of {}#{}", joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTime.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTime.java
new file mode 100644
index 0000000..00cfba6
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTime.java
@@ -0,0 +1,51 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.logging;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+
+@Aspect
+@Configuration
+public class ReverseProxyMethodLogTime {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReverseProxyMethodLogTime.class);
+
+ @Around("@annotation(org.onap.aaf.rproxy.logging.ReverseProxyMethodLogTimeAnnotation)")
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+
+ long startTime = System.currentTimeMillis();
+
+ Object object = joinPoint.proceed();
+
+ long duration = System.currentTimeMillis() - startTime;
+
+ LOGGER.info("Time taken by {} is {} ms", joinPoint, duration);
+
+ return object;
+ }
+
+
+
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTimeAnnotation.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTimeAnnotation.java
new file mode 100644
index 0000000..55f00f4
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/logging/ReverseProxyMethodLogTimeAnnotation.java
@@ -0,0 +1,30 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.logging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReverseProxyMethodLogTimeAnnotation {
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/mocks/ReverseProxyMockCadiFilter.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/mocks/ReverseProxyMockCadiFilter.java
new file mode 100644
index 0000000..10bfc3e
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/mocks/ReverseProxyMockCadiFilter.java
@@ -0,0 +1,143 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.mocks;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import org.onap.aaf.cadi.CadiWrap;
+import org.onap.aaf.cadi.Lur;
+import org.onap.aaf.cadi.Permission;
+import org.onap.aaf.cadi.aaf.AAFPermission;
+import org.onap.aaf.cadi.principal.TaggedPrincipal;
+import org.onap.aaf.cadi.taf.TafResp;
+
+public class ReverseProxyMockCadiFilter implements Filter {
+
+ private FakeLur fakeLur = new FakeLur();
+
+ static class FakeLur implements Lur {
+
+ @Override
+ public void fishAll(Principal bait, List<Permission> permissions) {
+
+ final String WildcardPermissionType = "test.wildcard.access";
+ final String MultiplePermissionType = "test.multiple.access";
+ final String TestAuthAccessPermissionType = "test.auth.access";
+ final String PermissionAction = "permission";
+
+ String principalName = bait.getName();
+
+ if (principalName != null && principalName.equals("UserWithInstanceActionWildcardPermissionGranted")) {
+ permissions.add(new AAFPermission(null, WildcardPermissionType, "*", "*"));
+ }
+ else
+ if (principalName != null && principalName.equals("UserWithInstanceWildcardPermissionGranted")) {
+ permissions.add(new AAFPermission(null, WildcardPermissionType, "*", PermissionAction));
+ }
+ else
+ if (principalName != null && principalName.equals("UserWithActionWildcardPermissionGranted")) {
+ permissions.add(new AAFPermission(null, WildcardPermissionType, "first", "*"));
+ }
+ else {
+
+ // For single permission test
+ permissions.add(new AAFPermission(null, "test.single.access", "single", PermissionAction));
+
+ // For multiple permission test
+ permissions.add(new AAFPermission(null, MultiplePermissionType, "first", PermissionAction));
+ permissions.add(new AAFPermission(null, MultiplePermissionType, "second", PermissionAction));
+ permissions.add(new AAFPermission(null, MultiplePermissionType, "third", PermissionAction));
+
+ // For transaction id test
+ permissions.add(new AAFPermission(null, TestAuthAccessPermissionType, "rest", "write"));
+ permissions.add(new AAFPermission(null, TestAuthAccessPermissionType, "rpc", "write"));
+ }
+ }
+
+ @Override
+ public Permission createPerm(String p) {
+ return null;
+ }
+
+ @Override
+ public boolean fish(Principal bait, Permission... pond) {
+ return false;
+ }
+
+ @Override
+ public void destroy() {
+ // Mock implementation
+ }
+
+ @Override
+ public boolean handlesExclusively(Permission... pond) {
+ return false;
+ }
+
+ @Override
+ public boolean handles(Principal principal) {
+ return false;
+ }
+
+ @Override
+ public void clear(Principal p, StringBuilder report) {
+ // Mock implementation
+ }
+
+ }
+
+ @Override
+ public void destroy() {
+ // Mock implementation
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ String userName = ((HttpServletRequest)servletRequest).getHeader("PermissionsUser");
+
+ TaggedPrincipal mockTaggedPrincipal = mock(TaggedPrincipal.class);
+ when(mockTaggedPrincipal.getName()).thenReturn(userName);
+
+ TafResp tafResponseMock = mock(TafResp.class);
+ when(tafResponseMock.getPrincipal()).thenReturn(mockTaggedPrincipal);
+
+ CadiWrap cadiWrap = new CadiWrap((HttpServletRequest) servletRequest, tafResponseMock, fakeLur);
+ filterChain.doFilter(cadiWrap, servletResponse);
+ }
+
+ @Override
+ public void init(FilterConfig arg0) throws ServletException {
+ // Mock implementation
+ }
+
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyAuthorization.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyAuthorization.java
new file mode 100644
index 0000000..6c5baa2
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyAuthorization.java
@@ -0,0 +1,34 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.utils;
+
+public class ReverseProxyAuthorization {
+
+ private String uri;
+ private String[] permissions;
+
+ public String getUri() {
+ return uri;
+ }
+
+ public String[] getPermissions() {
+ return permissions;
+ }
+}
diff --git a/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyUtils.java b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyUtils.java
new file mode 100644
index 0000000..d2d1ba4
--- /dev/null
+++ b/sidecar/rproxy/src/main/java/org/onap/aaf/rproxy/utils/ReverseProxyUtils.java
@@ -0,0 +1,29 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy.utils;
+
+public class ReverseProxyUtils {
+
+ public static boolean validTransactionId(String transactionId) {
+ return transactionId.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
+ }
+
+ private ReverseProxyUtils() {}
+}
diff --git a/sidecar/rproxy/src/main/resources/application.properties b/sidecar/rproxy/src/main/resources/application.properties
new file mode 100644
index 0000000..f291372
--- /dev/null
+++ b/sidecar/rproxy/src/main/resources/application.properties
@@ -0,0 +1,22 @@
+CONFIG_HOME=./config
+
+server.port=10692
+
+server.ssl.enabled=true
+server.ssl.protocol=TLS
+server.ssl.enabled-protocols=TLSv1.2
+server.ssl.key-store=${CONFIG_HOME}/auth/tomcat_keystore
+server.ssl.client-auth=need
+
+server.ssl.client-cert=${CONFIG_HOME}/auth/client-cert.p12
+
+server.servlet.contextPath=/
+
+uri.authorization.configuration-file=${CONFIG_HOME}/auth/uri-authorization.json
+
+logging.config=${CONFIG_HOME}/logback-spring.xml
+
+spring.profiles.default=secure,cadi
+
+# For Spring Boot Actuator endpoints
+management.endpoints.web.base-path=/rproxy \ No newline at end of file
diff --git a/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/PermissionMatchingTest.java b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/PermissionMatchingTest.java
new file mode 100644
index 0000000..43e20fe
--- /dev/null
+++ b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/PermissionMatchingTest.java
@@ -0,0 +1,262 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import javax.annotation.Resource;
+import org.eclipse.jetty.util.security.Password;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.onap.aaf.rproxy.config.ForwardProxyProperties;
+import org.onap.aaf.rproxy.config.PrimaryServiceProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.web.client.RestTemplate;
+
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+
+@TestPropertySource(locations = {"classpath:primary-service.properties", "classpath:forward-proxy.properties"})
+
+@ContextConfiguration(classes = ReverseProxyTestConfig.class)
+public class PermissionMatchingTest {
+
+ static {
+ System.setProperty("server.ssl.key-store-password",
+ Password.deobfuscate("OBF:1y0q1uvc1uum1uvg1pil1pjl1uuq1uvk1uuu1y10"));
+ }
+
+ @Value("${transactionid.header.name}")
+ private String transactionIdHeaderName;
+
+ @Resource(name = "PrimaryServiceProperties")
+ private PrimaryServiceProperties primaryServiceProps;
+
+ @Resource(name = "ForwardProxyProperties")
+ private ForwardProxyProperties forwardProxyProps;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ private MockRestServiceServer mockServer;
+
+ private String primaryServiceBaseUrl;
+
+ @Before
+ public void setUp() throws Exception {
+ mockServer = MockRestServiceServer.createServer(restTemplate);
+ primaryServiceBaseUrl = primaryServiceProps.getProtocol() + "://" + primaryServiceProps.getHost() + ":"
+ + primaryServiceProps.getPort();
+ }
+
+ @Test
+ public void testURIMismatch() throws Exception {
+
+ String testUrl = "/uri/does/not/exist";
+ String testResponse = "Sorry, the request is not allowed";
+
+ mockMvc
+ .perform(get(testUrl))
+ .andExpect(status().isForbidden())
+ .andExpect(status().reason(testResponse));
+
+ }
+
+ @Test
+ public void testURINoPermission() throws Exception {
+
+ String testUrl = "/not/allowed/at/all";
+ String testResponse = "Sorry, the request is not allowed";
+
+ mockMvc
+ .perform(get(testUrl))
+ .andExpect(status().isForbidden())
+ .andExpect(status().reason(testResponse));
+
+ }
+
+ @Test
+ public void testURIMatchSinglePermissionMatch() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/single/permission/required";
+ String testResponse = "Response from MockRestService";
+
+ mockServer
+ .expect(requestTo(primaryServiceBaseUrl + testUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(testUrl).accept(MediaType.APPLICATION_JSON).header(transactionIdHeaderName, transactionId))
+ .andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+
+ }
+
+ @Test
+ public void testURIMatchMultiplePermissionMatch() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/multiple/permissions/required";
+ String testResponse = "Response from MockRestService";
+
+ mockServer
+ .expect(requestTo(primaryServiceBaseUrl + testUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc
+ .perform(MockMvcRequestBuilders.get(testUrl).accept(MediaType.APPLICATION_JSON).header(transactionIdHeaderName, transactionId))
+ .andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+
+ }
+
+ @Test
+ public void testURIMatchMultipleMissingOnePermissionMatch() throws Exception {
+
+ String testUrl = "/multiple/permissions/required/one/missing";
+ String testResponse = "Sorry, the request is not allowed";
+
+ mockMvc
+ .perform(get(testUrl))
+ .andExpect(status().isForbidden())
+ .andExpect(status().reason(testResponse));
+ }
+
+ @Test
+ public void testURIInstanceActionWildCardPermissionMatch() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/wildcard/permission/granted";
+ String testResponse = "Response from MockRestService";
+
+ mockServer
+ .expect(requestTo(primaryServiceBaseUrl + testUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc
+ .perform(MockMvcRequestBuilders
+ .get(testUrl)
+ .accept(MediaType.APPLICATION_JSON)
+ .header(transactionIdHeaderName, transactionId)
+ .header("PermissionsUser", "UserWithInstanceActionWildcardPermissionGranted")
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+
+ }
+
+ @Test
+ public void testURIInstanceWildCardPermissionMatch() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/instance/wildcard/permission/granted";
+ String testResponse = "Response from MockRestService";
+
+ mockServer
+ .expect(requestTo(primaryServiceBaseUrl + testUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc
+ .perform(MockMvcRequestBuilders
+ .get(testUrl)
+ .accept(MediaType.APPLICATION_JSON)
+ .header(transactionIdHeaderName, transactionId)
+ .header("PermissionsUser", "UserWithInstanceWildcardPermissionGranted")
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+
+ }
+
+ @Test
+ public void testURIActionWildCardPermissionMatch() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/action/wildcard/permission/granted";
+ String testResponse = "Response from MockRestService";
+
+ mockServer
+ .expect(requestTo(primaryServiceBaseUrl + testUrl))
+ .andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc
+ .perform(MockMvcRequestBuilders
+ .get(testUrl)
+ .accept(MediaType.APPLICATION_JSON)
+ .header(transactionIdHeaderName, transactionId)
+ .header("PermissionsUser", "UserWithActionWildcardPermissionGranted")
+ )
+ .andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+
+ }
+
+}
diff --git a/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyApplicationTest.java b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyApplicationTest.java
new file mode 100644
index 0000000..61a0923
--- /dev/null
+++ b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyApplicationTest.java
@@ -0,0 +1,158 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import javax.annotation.Resource;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.security.Password;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.onap.aaf.rproxy.config.ForwardProxyProperties;
+import org.onap.aaf.rproxy.config.PrimaryServiceProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.test.web.client.match.MockRestRequestMatchers;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.web.client.RestTemplate;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+@TestPropertySource(locations = {"classpath:primary-service.properties", "classpath:forward-proxy.properties"})
+@ContextConfiguration(classes = ReverseProxyTestConfig.class)
+public class ReverseProxyApplicationTest {
+
+ static {
+ System.setProperty("server.ssl.key-store-password",
+ Password.deobfuscate("OBF:1y0q1uvc1uum1uvg1pil1pjl1uuq1uvk1uuu1y10"));
+ }
+
+ @Value("${transactionid.header.name}")
+ private String transactionIdHeaderName;
+
+ @Resource(name = "PrimaryServiceProperties")
+ private PrimaryServiceProperties primaryServiceProps;
+
+ @Resource(name = "ForwardProxyProperties")
+ private ForwardProxyProperties forwardProxyProps;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ private MockRestServiceServer mockServer;
+
+ private String primaryServiceBaseUrl;
+ private String forwardProxyBaseUrl;
+
+ @Before
+ public void setUp() throws Exception {
+ mockServer = MockRestServiceServer.createServer(restTemplate);
+ primaryServiceBaseUrl = primaryServiceProps.getProtocol() + "://" + primaryServiceProps.getHost() + ":"
+ + primaryServiceProps.getPort();
+ forwardProxyBaseUrl = forwardProxyProps.getProtocol() + "://" + forwardProxyProps.getHost() + ":"
+ + forwardProxyProps.getPort() + forwardProxyProps.getCacheurl() + "/";
+ }
+
+ @Test
+ public void checkForwardRequest() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/aai/v13/cloud-infrastructure/cloud-regions";
+ String testResponse = "Response from MockRestService";
+
+ mockServer.expect(requestTo(primaryServiceBaseUrl + testUrl)).andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc.perform(MockMvcRequestBuilders.get(testUrl).accept(MediaType.APPLICATION_JSON)
+ .header(transactionIdHeaderName, transactionId)).andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+ }
+
+ @Test
+ public void checkTransactionIdIsSetIfEmptyInRequest() throws Exception {
+
+ String testUrl = "/aai/v13/cloud-infrastructure/cloud-regions";
+ String testResponse = "Response from MockRestService";
+
+ mockServer.expect(requestTo(primaryServiceBaseUrl + testUrl)).andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, Matchers.any(String.class)))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server without transaction Id
+ mockMvc.perform(MockMvcRequestBuilders.get(testUrl).accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk()).andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+ }
+
+ @Test
+ public void checkBasicAuthCaching() throws Exception {
+
+ String transactionId = "63f88b50-6345-4a61-bc59-3a48cabb60a4";
+ String testUrl = "/aai/v13/cloud-infrastructure/cloud-regions";
+ String testResponse = "Response from MockRestService";
+ String testAuthValue = "testAuthValue";
+ String testCachePayload = "{ \"credentialName\":" + HttpHeader.AUTHORIZATION + ", \"credentialValue\":"
+ + testAuthValue + ", \"credentialType\":\"HEADER\" }";
+
+ mockServer.expect(requestTo(forwardProxyBaseUrl + transactionId)).andExpect(method(HttpMethod.POST))
+ .andExpect(MockRestRequestMatchers.content().json(testCachePayload))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ mockServer.expect(requestTo(primaryServiceBaseUrl + testUrl)).andExpect(method(HttpMethod.GET))
+ .andExpect(header(transactionIdHeaderName, transactionId))
+ .andRespond(withSuccess(testResponse, MediaType.APPLICATION_JSON));
+
+ // Send request to mock server with transaction Id
+ mockMvc.perform(MockMvcRequestBuilders.get(testUrl).accept(MediaType.APPLICATION_JSON)
+ .header(transactionIdHeaderName, transactionId)
+ .header(HttpHeader.AUTHORIZATION.asString(), testAuthValue)).andExpect(status().isOk())
+ .andExpect(content().string(equalTo(testResponse)));
+
+ mockServer.verify();
+ }
+}
diff --git a/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyIT.java b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyIT.java
new file mode 100644
index 0000000..615afd4
--- /dev/null
+++ b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyIT.java
@@ -0,0 +1,48 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.eclipse.jetty.util.security.Password;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+public class ReverseProxyIT {
+
+ static {
+ System.setProperty("server.ssl.key-store-password",
+ Password.deobfuscate("OBF:1y0q1uvc1uum1uvg1pil1pjl1uuq1uvk1uuu1y10"));
+ }
+
+ @Autowired
+ private ReverseProxyService rProxyService;
+
+ @Test
+ public void contexLoads() throws Exception {
+ assertThat(rProxyService).isNotNull();
+ }
+}
diff --git a/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyTestConfig.java b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyTestConfig.java
new file mode 100644
index 0000000..c512e6a
--- /dev/null
+++ b/sidecar/rproxy/src/test/java/org/onap/aaf/rproxy/ReverseProxyTestConfig.java
@@ -0,0 +1,42 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aaf
+ * ================================================================================
+ * Copyright © 2018 European Software Marketing Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aaf.rproxy;
+
+import org.onap.aaf.rproxy.mocks.ReverseProxyMockCadiFilter;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.RegistrationBean;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+
+@TestConfiguration
+public class ReverseProxyTestConfig extends SpringBootServletInitializer {
+
+ @Bean
+ public FilterRegistrationBean<ReverseProxyMockCadiFilter> registerCADIFilter() {
+ FilterRegistrationBean<ReverseProxyMockCadiFilter> filterRegistrationBean = new FilterRegistrationBean<>();
+ filterRegistrationBean.setFilter(new ReverseProxyMockCadiFilter());
+ filterRegistrationBean.addUrlPatterns("/*");
+ filterRegistrationBean.setName("CADIFilter");
+ filterRegistrationBean.setOrder(RegistrationBean.HIGHEST_PRECEDENCE);
+
+ return filterRegistrationBean;
+ }
+}
diff --git a/sidecar/rproxy/src/test/resources/forward-proxy.properties b/sidecar/rproxy/src/test/resources/forward-proxy.properties
new file mode 100644
index 0000000..119ccbd
--- /dev/null
+++ b/sidecar/rproxy/src/test/resources/forward-proxy.properties
@@ -0,0 +1,4 @@
+forward-proxy.protocol = https
+forward-proxy.host = localhost
+forward-proxy.port = 9501
+forward-proxy.cacheurl = /credential-cache \ No newline at end of file
diff --git a/sidecar/rproxy/src/test/resources/primary-service.properties b/sidecar/rproxy/src/test/resources/primary-service.properties
new file mode 100644
index 0000000..134e103
--- /dev/null
+++ b/sidecar/rproxy/src/test/resources/primary-service.properties
@@ -0,0 +1,3 @@
+primary-service.protocol = https
+primary-service.host = localhost
+primary-service.port = 9500
diff --git a/sidecar/rproxy/version.properties b/sidecar/rproxy/version.properties
new file mode 100644
index 0000000..14e3c2b
--- /dev/null
+++ b/sidecar/rproxy/version.properties
@@ -0,0 +1,13 @@
+# Versioning variables
+# Note that these variables cannot be structured (e.g. : version.release or version.snapshot etc... )
+# because they are used in Jenkins, whose plug-in doesn't support
+
+major=1
+minor=0
+patch=0
+
+base_version=${major}.${minor}.${patch}
+
+# Release must be completed with git revision # in Jenkins
+release_version=${base_version}
+snapshot_version=${base_version}-SNAPSHOT \ No newline at end of file