Repository: openshift-pipelines/pipelines-as-code
Pull request #276 (2021-10-07 12:14:35)

add bitbucket-cloud-additional-source-ip setting
Allow specify additional cloud ip if we are behind a proxy.

Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>



Patch:
diff --git a/config/302-pac-configmap.yaml b/config/302-pac-configmap.yaml
index 7f72824d..3878a313 100644
--- a/config/302-pac-configmap.yaml
+++ b/config/302-pac-configmap.yaml
@@ -33,6 +33,9 @@ data:
   # look like a authorized owner has send the PR to run it..
   bitbucket-cloud-check-source-ip: "true"
 
+  # Add extra IPS (ie: 127.0.0.1) or networks (127.0.0.0/16) separated by commas.
+  bitbucket-cloud-additional-source-ip: ""
+
 kind: ConfigMap
 metadata:
   name: pipelines-as-code
diff --git a/config/4022-bitbucket-cloud-pull-request.yaml b/config/4022-bitbucket-cloud-pull-request.yaml
index 75b0fe11..7d4da551 100644
--- a/config/4022-bitbucket-cloud-pull-request.yaml
+++ b/config/4022-bitbucket-cloud-pull-request.yaml
@@ -178,6 +178,11 @@ spec:
                           configMapKeyRef:
                             name: pipelines-as-code
                             key: bitbucket-cloud-check-source-ip
+                      - name: PAC_BITBUCKET_CLOUD_ADDITIONAL_SOURCE_IP
+                        valueFrom:
+                          configMapKeyRef:
+                            name: pipelines-as-code
+                            key: bitbucket-cloud-additional-source-ip
                       - name: PAC_WEBVCS_TYPE
                         value: "bitbucket-cloud"
                     imagePullPolicy: Always
diff --git a/config/4023-bitbucket-cloud-push.yaml b/config/4023-bitbucket-cloud-push.yaml
index 9e3eeacf..a52e0873 100644
--- a/config/4023-bitbucket-cloud-push.yaml
+++ b/config/4023-bitbucket-cloud-push.yaml
@@ -65,6 +65,7 @@ spec:
     - name: actor_nickname
     - name: branch_new_name
     - name: branch_old_name
+      default: ""
     - name: push_new_hash
   resourcetemplates:
     - apiVersion: tekton.dev/v1beta1
@@ -155,6 +156,11 @@ spec:
                           configMapKeyRef:
                             name: pipelines-as-code
                             key: bitbucket-cloud-check-source-ip
+                      - name: PAC_BITBUCKET_CLOUD_ADDITIONAL_SOURCE_IP
+                        valueFrom:
+                          configMapKeyRef:
+                            name: pipelines-as-code
+                            key: bitbucket-cloud-additional-source-ip
                       - name: PAC_WEBVCS_TYPE
                         value: "bitbucket-cloud"
                     imagePullPolicy: Always
diff --git a/docs/INSTALL.md b/docs/INSTALL.md
index 43fe0d07..cdbc33b6 100644
--- a/docs/INSTALL.md
+++ b/docs/INSTALL.md
@@ -125,23 +125,24 @@ detects the header as set from GHE and use it the GHE API auth url instead of th
 If you are not able to create a GitHub application you can install Pipelines-as-Code on your repository as a
 [GitHub Webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks).
 
-Using Pipelines as Code via Github webhook does not give you access to the GitHub CheckRun API,
-therefore the status of the tasks will be added as a Comment of the PR and not via the **Checks** Tab.
+Using Pipelines as Code via Github webhook does not give you access to the GitHub CheckRun API, therefore the status of
+the tasks will be added as a Comment of the PR and not via the **Checks** Tab.
 
-* You have to first install the Pipelines-as-Code infrastructure as detailled here : [Install infrastructure](INSTALL.md#install-pipelines-as-code-infrastructure)
+* You have to first install the Pipelines-as-Code infrastructure as detailled
+  here : [Install infrastructure](INSTALL.md#install-pipelines-as-code-infrastructure)
 
-* You will have to generate a personal token for Pipelines-as-Code Github API operations.
-Follow this guide to create a personal token :
+* You will have to generate a personal token for Pipelines-as-Code Github API operations. Follow this guide to create a
+  personal token :
 
 <https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token>
 
-  The only permission needed is the *repo* permission. Make sure you note
-  somewhere the generated token or otherwise you will have to recreate it.
+The only permission needed is the *repo* permission. Make sure you note somewhere the generated token or otherwise you
+will have to recreate it.
 
 * Go to you repository or organisation setting and click on *Hooks* and *"Add webhook"* links.
 
-* Set the payload URL to the event listenner public URL.
-  On OpenShift you can get the public URL of the Pipelines-as-Code eventlistenner like this :
+* Set the payload URL to the event listenner public URL. On OpenShift you can get the public URL of the
+  Pipelines-as-Code eventlistenner like this :
 
   ```shell
   echo https://$(oc get route -n pipelines-as-code el-pipelines-as-code-interceptor -o jsonpath='{.spec.host}')
@@ -153,12 +154,13 @@ Follow this guide to create a personal token :
   openssl rand -hex 20
   ```
 
-* [Refer to this screenshot](./images/pac-direct-webhook-create.png) on how to configure the Webhook. The individual events to select are :
-  * Commit comments
-  * Issue comments
-  * Pull request reviews
-  * Pull request
-  * Pushes
+* [Refer to this screenshot](./images/pac-direct-webhook-create.png) on how to configure the Webhook. The individual
+  events to select are :
+    * Commit comments
+    * Issue comments
+    * Pull request reviews
+    * Pull request
+    * Pushes
 
 * On your cluster you need create the webhook secret as generated previously in the *pipelines-as-code* namespace.
 
@@ -167,16 +169,16 @@ kubectl -n pipelines-as-code create secret generic github-app-secret \
         --from-literal webhook.secret="$WEBHOOK_SECRET_AS_GENERATED"
 ```
 
-* You are now able to create a Repository CRD. The repository CRD will have a Secret that contains the Personal token
-  as generated and Pipelines as Code will know how to use it for GitHub API operations.
+* You are now able to create a Repository CRD. The repository CRD will have a Secret that contains the Personal token as
+  generated and Pipelines as Code will know how to use it for GitHub API operations.
 
-  - First create the secret with the personal token in the `target-namespace` :
+    - First create the secret with the personal token in the `target-namespace` :
   ```shell
   kubectl -n target-namespace create secret generic github-personal-token \
           --from-literal token="TOKEN_AS_GENERATED_PREVIOUSLY"
   ```
 
-  - And now create Repositry CRD with the secret field referencing it.
+    - And now create Repositry CRD with the secret field referencing it.
 
   Here is an example of a Repository CRD :
 
@@ -199,54 +201,56 @@ spec:
     # key: "token"
 ```
 
-* Note that `webvcs_secret` cannot reference a secret in another namespace, Pipelines as
-code assumes always it will be the same namespace as where the repository has been
-created.
+* Note that `webvcs_secret` cannot reference a secret in another namespace, Pipelines as code assumes always it will be
+  the same namespace as where the repository has been created.
 
 ## Install Pipelines-As-Code for Bitbucket Cloud
 
 Pipelines-As-Code has a full support on Bitbucket Cloud on <https://bitbucket.org>
 
-* You have to first install the Pipelines-as-Code infrastructure as detailled here : [Install infrastructure](INSTALL.md#install-pipelines-as-code-infrastructure)
+* You have to first install the Pipelines-as-Code infrastructure as detailled
+  here : [Install infrastructure](INSTALL.md#install-pipelines-as-code-infrastructure)
 
-* You will have to generate an app password for Pipelines-as-Code Bitbucket API operations.
-  Follow this guide to create an app password :
+* You will have to generate an app password for Pipelines-as-Code Bitbucket API operations. Follow this guide to create
+  an app password :
 
 <https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/>
 
-  Make sure you note somewhere the generated token or otherwise you will have to recreate it.
+Make sure you note somewhere the generated token or otherwise you will have to recreate it.
 
-* Go to you **"Repository setting"** tab on your **Repository** and click on the **WebHooks** tab and **"Add webhook"** button.
+* Go to you **"Repository setting"** tab on your **Repository** and click on the **WebHooks** tab and **"Add webhook"**
+  button.
 
 * Set a **Title** (i.e: Pipelines as Code)
 
-* Set the URL to the event listenner public URL.
-  On OpenShift you can get the public URL of the Pipelines-as-Code eventlistenner like this :
+* Set the URL to the event listenner public URL. On OpenShift you can get the public URL of the Pipelines-as-Code
+  eventlistenner like this :
 
   ```shell
   echo https://$(oc get route -n pipelines-as-code el-pipelines-as-code-interceptor -o jsonpath='{.spec.host}')
   ```
 
-* [Refer to this screenshot](./images/bitbucket-cloud-create-webhook.png) on how to configure the Webhook. The individual events to select are :
-  * Repository -> Push
-  * Pull Request -> Created
-  * Pull Request -> Updated
-  * Pull Request -> Comment created
-  * Pull Request -> Comment updated
+* [Refer to this screenshot](./images/bitbucket-cloud-create-webhook.png) on how to configure the Webhook. The
+  individual events to select are :
+    * Repository -> Push
+    * Pull Request -> Created
+    * Pull Request -> Updated
+    * Pull Request -> Comment created
+    * Pull Request -> Comment updated
 
 
-* You are now able to create a Repository CRD. The repository CRD will have a Secret and Username that contains the App Password
-  as generated and Pipelines as Code will know how to use it for Bitbucket API operations.
+* You are now able to create a Repository CRD. The repository CRD will have a Secret and Username that contains the App
+  Password as generated and Pipelines as Code will know how to use it for Bitbucket API operations.
 
-  - First create the secret with the app password in the `target-namespace` :
+    - First create the secret with the app password in the `target-namespace` :
   ```shell
   kubectl -n target-namespace create secret generic bitbucket-app-password \
           --from-literal token="TOKEN_AS_GENERATED_PREVIOUSLY"
   ```
 
-  - And now create Repositry CRD with the secret field referencing it.
+    - And now create Repositry CRD with the secret field referencing it.
 
-  - Here is an example of a Repository CRD :
+    - Here is an example of a Repository CRD :
 
 ```yaml
 ---
@@ -266,16 +270,17 @@ spec:
     # key: "token"
 ```
 
-* Note that `webvcs_secret` cannot reference a secret in another namespace, Pipelines as
-  code assumes always it will be the same namespace as where the repository has been
-  created.
-
-* There is no Webhook secret support in Bitbucket Cloud.  To be able to secure
-  the payload and not let a user hijack the CI, Pipelines-as-Code will fetch
-  the ip addresses list from <https://ip-ranges.atlassian.com/> and make sure
-  the webhook only comes from the Bitbucket Cloud IPS.  If you want to disable
-  this behaviour you can set the key **bitbucket-cloud-check-source-ip** to
-  false in the pipelines-as-code configmap in the pipelines-as-code namespace.
+* Note that `webvcs_secret` cannot reference a secret in another namespace, Pipelines as code assumes always it will be
+  the same namespace as where the repository has been created.
+
+* There is no Webhook secret support in Bitbucket Cloud. To be able to secure the payload and not let a user hijack the
+  CI, Pipelines-as-Code will fetch the ip addresses list from <https://ip-ranges.atlassian.com/> and make sure the
+  webhook only comes from the Bitbucket Cloud IPS.
+    * If you want to add some ips address or networks you can add them to the key
+      **bitbucket-cloud-additional-source-ip** in the pipelines-as-code configmap in the pipelines-as-code namespace.
+      You can added multiple network or ips separated by a comma.
+    * If you want to disable this behaviour you can set the key **bitbucket-cloud-check-source-ip** to false in the
+      pipelines-as-code configmap in the pipelines-as-code namespace.
 
 ## Pipelines-As-Code configuration settings.
 
diff --git a/pkg/webvcs/bitbucketcloud/events.go b/pkg/webvcs/bitbucketcloud/events.go
index 76d80103..0155f179 100644
--- a/pkg/webvcs/bitbucketcloud/events.go
+++ b/pkg/webvcs/bitbucketcloud/events.go
@@ -25,10 +25,7 @@ func lastForwarderForIP(xff string) string {
 // checkFromPublicCloudIPS Grab public IP from public cloud and make sure we match it
 func (v *VCS) checkFromPublicCloudIPS(ctx context.Context, run *params.Run) (bool, error) {
 	enval, ok := os.LookupEnv("PAC_BITBUCKET_CLOUD_CHECK_SOURCE_IP")
-	if !ok {
-		return true, nil
-	}
-	if strings.ToLower(enval) != "true" {
+	if !ok || strings.ToLower(enval) != "true" {
 		return true, nil
 	}
 
@@ -49,6 +46,18 @@ func (v *VCS) checkFromPublicCloudIPS(ctx context.Context, run *params.Run) (boo
 	if err != nil {
 		return false, err
 	}
+
+	extraIPEnv, _ := os.LookupEnv("PAC_BITBUCKET_CLOUD_ADDITIONAL_SOURCE_IP")
+	if extraIPEnv != "" {
+		for _, value := range strings.Split(extraIPEnv, ",") {
+			if !strings.Contains(value, "/") {
+				value = fmt.Sprintf("%s/32", value)
+			}
+			ipranges.Items = append(ipranges.Items, types.IPRangesItem{
+				CIDR: strings.TrimSpace(value),
+			})
+		}
+	}
 	for _, value := range ipranges.Items {
 		_, cidr, err := net.ParseCIDR(value.CIDR)
 		if err != nil {


Test patch:
diff --git a/hack/dev/bitbucket-cloud-pr-test.sh b/hack/dev/bitbucket-cloud-pr-test.sh
index 39139922..cfe07bc7 100755
--- a/hack/dev/bitbucket-cloud-pr-test.sh
+++ b/hack/dev/bitbucket-cloud-pr-test.sh
@@ -50,9 +50,9 @@ PAYLOAD_FILE=/tmp/payload-pullrequest-${OWNER}-${REPOSITORY}.json
 
 export PAC_WEBVCS_TYPE="bitbucket-cloud"
 export PAC_SECRET_AUTO_CREATE=true
-export PAC_BITBUCKET_CHECK_SOURCE_IP=true
-export PAC_SOURCE_IP="127.0.0.1,18.246.31.224"
-
+export PAC_BITBUCKET_CLOUD_CHECK_SOURCE_IP=true
+export PAC_SOURCE_IP="1.2.3.4,127.0.0.1"
+export PAC_BITBUCKET_CLOUD_ADDITIONAL_SOURCE_IP="127.0.0.1"
 
 clean() { rm -f "${TMP}" ;}
 trap clean EXIT
diff --git a/pkg/webvcs/bitbucketcloud/events_test.go b/pkg/webvcs/bitbucketcloud/events_test.go
index 11b1c5fa..36dee015 100644
--- a/pkg/webvcs/bitbucketcloud/events_test.go
+++ b/pkg/webvcs/bitbucketcloud/events_test.go
@@ -15,15 +15,16 @@ import (
 
 func TestParsePayload1(t *testing.T) {
 	tests := []struct {
-		name              string
-		payloadEvent      interface{}
-		wantErr           bool
-		expectedSender    string
-		expectedAccountID string
-		expectedSHA       string
-		eventType         string
-		sourceIP          string
-		allowedConfig     map[string]map[string]string
+		name                      string
+		payloadEvent              interface{}
+		wantErr                   bool
+		expectedSender            string
+		expectedAccountID         string
+		expectedSHA               string
+		eventType                 string
+		sourceIP                  string
+		allowedConfig             map[string]map[string]string
+		additionalAllowedsourceIP string
 	}{
 		{
 			name:              "parse push request",
@@ -88,14 +89,57 @@ func TestParsePayload1(t *testing.T) {
 			},
 		},
 		{
-			name:              "check xff hijack",
-			wantErr:           true,
-			payloadEvent:      bbcloudtest.MakePREvent("account", "sender", "sha"),
-			expectedAccountID: "account",
-			expectedSender:    "sender",
-			expectedSHA:       "sha",
-			eventType:         "pull_request",
-			sourceIP:          "1.2.3.1,127.0.0.1",
+			name:                      "additional source ip allowed",
+			payloadEvent:              bbcloudtest.MakePREvent("account", "sender", "sha"),
+			expectedAccountID:         "account",
+			expectedSender:            "sender",
+			expectedSHA:               "sha",
+			eventType:                 "pull_request",
+			sourceIP:                  "1.2.3.1",
+			additionalAllowedsourceIP: "1.2.3.1",
+			allowedConfig: map[string]map[string]string{
+				bitbucketCloudIPrangesList: {
+					"body": `{"items": [{"cidr": "10.12.33.22/16"}]}`,
+					"code": "200",
+				},
+			},
+		},
+		{
+			name:                      "additional network allowed with spaces",
+			payloadEvent:              bbcloudtest.MakePREvent("account", "sender", "sha"),
+			expectedAccountID:         "account",
+			expectedSender:            "sender",
+			expectedSHA:               "sha",
+			eventType:                 "pull_request",
+			sourceIP:                  "1.2.3.3",
+			additionalAllowedsourceIP: "1.2.3.4, 1.2.3.0/16",
+			allowedConfig: map[string]map[string]string{
+				bitbucketCloudIPrangesList: {
+					"body": `{"items": [{"cidr": "10.12.33.22/16"}]}`,
+					"code": "200",
+				},
+			},
+		},
+		{
+			name:                      "not allowed with additional ips",
+			wantErr:                   true,
+			payloadEvent:              bbcloudtest.MakePREvent("account", "sender", "sha"),
+			eventType:                 "pull_request",
+			sourceIP:                  "1.2.3.3",
+			additionalAllowedsourceIP: "1.1.3.0/16",
+			allowedConfig: map[string]map[string]string{
+				bitbucketCloudIPrangesList: {
+					"body": `{"items": [{"cidr": "10.12.33.22/16"}]}`,
+					"code": "200",
+				},
+			},
+		},
+		{
+			name:         "check xff hijack",
+			wantErr:      true,
+			payloadEvent: bbcloudtest.MakePREvent("account", "sender", "sha"),
+			eventType:    "pull_request",
+			sourceIP:     "1.2.3.1,127.0.0.1",
 			allowedConfig: map[string]map[string]string{
 				bitbucketCloudIPrangesList: {
 					"body": `{"items": [{"cidr": "1.2.3.10/16"}]}`,
@@ -118,14 +162,23 @@ func TestParsePayload1(t *testing.T) {
 				},
 			}
 
+			envDict := map[string]string{
+				"PAC_BITBUCKET_CLOUD_CHECK_SOURCE_IP": "",
+				"PAC_SOURCE_IP":                       "",
+			}
 			if tt.sourceIP != "" {
-				defer env.PatchAll(t, map[string]string{
+				envDict = map[string]string{
 					"PAC_BITBUCKET_CLOUD_CHECK_SOURCE_IP": "True",
 					"PAC_SOURCE_IP":                       tt.sourceIP,
-				})()
+				}
+				if tt.additionalAllowedsourceIP != "" {
+					envDict["PAC_BITBUCKET_CLOUD_ADDITIONAL_SOURCE_IP"] = tt.additionalAllowedsourceIP
+				}
+
 				httpTestClient := httptesthelper.MakeHTTPTestClient(t, tt.allowedConfig)
 				run.Clients.HTTP = *httpTestClient
 			}
+			defer env.PatchAll(t, envDict)()
 
 			payload, err := json.Marshal(tt.payloadEvent)
 			assert.NilError(t, err)
@@ -134,6 +187,8 @@ func TestParsePayload1(t *testing.T) {
 				assert.Assert(t, err != nil)
 				return
 			}
+			assert.NilError(t, err)
+			assert.Assert(t, got != nil)
 			assert.Equal(t, tt.expectedAccountID, got.AccountID)
 			assert.Equal(t, tt.expectedSender, got.Sender)
 			assert.Equal(t, tt.expectedSHA, got.SHA, "%s != %s", tt.expectedSHA, got.SHA)