├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets └── diagram.png ├── code ├── index.mjs ├── package-lock.json ├── package.json └── yarn.lock ├── infra ├── infra.yaml ├── parameters.json └── params.json ├── package-lock.json └── package.yaml /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,node,linux,windows 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### Node ### 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Runtime data 28 | pids 29 | *.pid 30 | *.seed 31 | *.pid.lock 32 | 33 | # Directory for instrumented libs generated by jscoverage/JSCover 34 | lib-cov 35 | 36 | # Coverage directory used by tools like istanbul 37 | coverage 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (http://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Typescript v1 declaration files 59 | typings/ 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | 79 | 80 | ### OSX ### 81 | *.DS_Store 82 | .AppleDouble 83 | .LSOverride 84 | 85 | # Icon must end with two \r 86 | Icon 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear in the root of a volume 92 | .DocumentRevisions-V100 93 | .fseventsd 94 | .Spotlight-V100 95 | .TemporaryItems 96 | .Trashes 97 | .VolumeIcon.icns 98 | .com.apple.timemachine.donotpresent 99 | 100 | # Directories potentially created on remote AFP share 101 | .AppleDB 102 | .AppleDesktop 103 | Network Trash Folder 104 | Temporary Items 105 | .apdisk 106 | 107 | ### Windows ### 108 | # Windows thumbnail cache files 109 | Thumbs.db 110 | ehthumbs.db 111 | ehthumbs_vista.db 112 | 113 | # Folder config file 114 | Desktop.ini 115 | 116 | # Recycle Bin used on file shares 117 | $RECYCLE.BIN/ 118 | 119 | # Windows Installer files 120 | *.cab 121 | *.msi 122 | *.msm 123 | *.msp 124 | 125 | # Windows shortcuts 126 | *.lnk 127 | 128 | 129 | # End of https://www.gitignore.io/api/osx,node,linux,windows -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-codepipeline-bitbucket-integration/issues), or [recently closed](https://github.com/aws-samples/aws-codepipeline-bitbucket-integration/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-codepipeline-bitbucket-integration/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-codepipeline-bitbucket-integration/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2019 Amazon.com, Inc. or its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodePipeline Integration with Bitbucket Server 2 | 3 | This blog post demonstrates how to integrate AWS CodePipeline with on-premises Bitbucket Server. If you want to integrate with Bitbucket Cloud, see [AWS CodePipeline Now Supports Atlassian Bitbucket Cloud](https://aws.amazon.com/about-aws/whats-new/2019/12/aws-codepipeline-now-supports-atlassian-bitbucket-cloud/) (Beta). The [AWS Lambda](http://aws.amazon.com/lambda) function provided can get the source code from a Bitbucket Server repository whenever the user sends a new code push and store it in a designed [Amazon Simple Storage Service (Amazon S3) ](https://aws.amazon.com/s3/) bucket. 4 | 5 | The Bitbucket Server integration uses webhooks configured in the Bitbucket repository. Webhooks are ideal for this case and avoid the need for performing frequent polling to check for changes in the repository. 6 | 7 | Some security protections are available with this solution: 8 | 9 | * The [Amazon S3](https://aws.amazon.com/s3/) bucket has encryption enabled using SSE-AES, and every object created is encrypted by default 10 | * The Lambda function accepts only events signed by the Bitbucket Server 11 | * All environment variables used by the Lambda function are encrypted in rest using [AWS KMS](https://aws.amazon.com/kms/) 12 | 13 | ## Overview 14 | During the creation of the CloudFormation stack, you can select either Amazon API Gateway or Elastic Load Balancing to communicate with the Lambda function. The following diagram shows how the integration works. 15 | 16 | ![Solution Diagram](assets/diagram.png) 17 | 18 | 1. The user pushes code to the Bitbucket repository. 19 | 20 | 1. Based on that user action, the Bitbucket server generates a new webhook event and sends it to Elastic Load Balancing or API Gateway based on which endpoint type you selected during [AWS CloudFormation](https://aws.amazon.com/cloudformation/) stack creation. 21 | 22 | 1. API Gateway or Elastic Load Balancing forwards the request to the Lambda function, which checks the message signature using the secret configured in the webhook. If the signature is valid, then the Lambda function moves to the next step. 23 | 24 | 1. The Lambda function calls the Bitbucket server API and requests that it generate a ZIP package with the content of the branch modified by the user in Step 1. 25 | 26 | 1. The Lambda function sends the ZIP package to the Amazon S3 bucket. 27 | 28 | 1. CodePipeline is triggered when it detects a new or updated file in the Amazon S3 bucket path. 29 | 30 | ## Requirements 31 | 32 | Before starting the solution setup, make sure you have: 33 | 34 | 35 | * An Amazon S3 bucket available to store the Lambda function setup files 36 | * NPM or Yarn to install the package dependencies 37 | * [AWS CLI](https://aws.amazon.com/cli/) 38 | 39 | ## Setup 40 | 41 | Follow these steps for setup. 42 | 43 | ### Creating a personal token on the Bitbucket server 44 | 45 | Create a personal token on the Bitbucket server that the Lambda function uses to access the repository. 46 | 47 | 1. Log in to the Bitbucket server. 48 | 1. Choose your user avatar, then choose **Manage Account**. 49 | 1. On the **Account** screen, choose **Personal access tokens**. 50 | 1. Choose **Create a token**. 51 | 1. Fill out the form with the token name. In the **Permissions** section, leave **Read for Projects and Repositories** as is. 52 | 1. Choose **Create** to finish. 53 | 54 | ### Launch a CloudFormation stack 55 | Using the steps below you will upload the Lambda function and Lambda layer ZIP files to an Amazon S3 bucket and launch the AWS CloudFormation stack to create the resources on your AWS account. 56 | 57 | 1. Clone the Git repository containing the solution source code: 58 | ```bash 59 | git clone https://github.com/aws-samples/aws-codepipeline-bitbucket-integration.git 60 | ``` 61 | 62 | 1. Install the NodeJS packages with npm: 63 | 64 | ```bash 65 | cd code 66 | npm install 67 | cd .. 68 | ``` 69 | 70 | 1. Prepare the packages for deployment. 71 | 72 | ```bash 73 | aws cloudformation package --template-file ./infra/infra.yaml --s3-bucket your_bucket_name --output-template-file package.yaml 74 | ``` 75 | 76 | 1. Edit the AWS CloudFormation parameters file. 77 | 78 | Open the file located at infra/parameters.json in your favorite text editor and replace the parameters accordingly. 79 | 80 | Parameter Name | Description 81 | ------------ | ------------- 82 | BitbucketSecret | Bitbucket webhook secret used to sign webhook events. You should define the secret and use the same value here and in the Bitbucket server webhook. 83 | BitbucketServerUrl | URL of your Bitbucket Server, such as https://server:port. 84 | BitbucketToken | Bitbucket server personal token used by the Lambda function to access the Bitbucket API. 85 | EndpointType | Select the type of endpoint to integrate with the Lambda function. It can be the Application Load Balancer or the API Gateway. 86 | LambdaSubnets | Subnets where the Lambda function runs. 87 | LBCIDR | CIDR allowed to communicate with the Load Balancer. It should allow the Bitbucket server IP address. Leave it blank if you are using the API Gateway endpoint type. 88 | LBSubnets | Subnets where the Application Load Balancer runs. Leave it blank if you are using the API Gateway endpoint type. 89 | LBSSLCertificateArn | SSL Certificate to associate with the Application Load Balancer. Leave it blank if you are using the API Gateway endpoint type. 90 | S3BucketCodePipelineName | Amazon S3 bucket name that this stack creates to store the Bitbucket repository content. 91 | VPCID | VPC ID where the Application Load Balancer and the Lambda function run. 92 | WebProxyHost | Hostname of your proxy server used by the Lambda function to access the Bitbucket server, such as myproxy.mydomain.com. If you don’t need a web proxy, leave it blank. 93 | WebProxyPort | Port of your proxy server used by the Lambda function to access the Bitbucket server, such as 8080. If you don’t need a web proxy leave it blank. 94 | 95 | 5. Create the CloudFormation stack: 96 | 97 | ```bash 98 | aws cloudformation create-stack --stack-name CodePipeline-Bitbucket-Integration --template-body file://package.yaml --parameters file://infra/parameters.json --capabilities CAPABILITY_NAMED_IAM 99 | ``` 100 | 101 | ### Creating a webhook on the Bitbucket Server 102 | 103 | Next, create the webhook on Bitbucket server to notify the Lambda function of push events to the repository: 104 | 105 | 1. Log into the Bitbucket server and navigate to the repository page. 106 | 1. Choose **Repository settings**. 107 | 1. Select **Webhook**. 108 | 1. Choose **Create webhook**. 109 | 1. Fill out the form with the name of the webhook, such as CodePipeline. 110 | 1. Fill out the **URL** field with the API Gateway or Load Balancer URL. To obtain this URL, choose the **Outputs** tab of the AWS CloudFormation stack. 111 | 1. Fill out the Secret field with the same value used in the AWS CloudFormation stack. 112 | 1. In the **Events** section, ensure Push is selected. 113 | 1. Choose **Create** to finish. 114 | 1. Repeat these steps for each repository in which you want to enable the integration. 115 | 116 | ### Configure your pipeline 117 | Finally, change your pipeline on CodePipeline to use the Amazon S3 bucket created by the AWS CloudFormation stack as the source of your pipeline. 118 | 119 | The Lambda function uploads the files to the Amazon S3 bucket using the following path structure: 120 | 121 | ``` 122 | Project Name/Repository Name/Branch Name.zip 123 | ``` 124 | 125 | Now, every time someone pushes code to the Bitbucket repository, your pipeline starts automatically. 126 | 127 | ## Cleaning up 128 | If you want to remove the integration and clean up the resources created at AWS, you need to delete the CloudFormation stack. Run the command below to delete the stack and associated resources. 129 | 130 | ```bash 131 | aws cloudformation delete-stack --stack-name CodePipeline-Bitbucket-Integration 132 | ``` 133 | 134 | ## Conclusion 135 | This post demonstrated how to integrate your on-premises Bitbucket Server with CodePipeline. -------------------------------------------------------------------------------- /assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-codepipeline-bitbucket-integration/82b5de79f331ac7503af08b89a7d0e3059223f78/assets/diagram.png -------------------------------------------------------------------------------- /code/index.mjs: -------------------------------------------------------------------------------- 1 | import { S3Client } from '@aws-sdk/client-s3'; 2 | import { Upload } from "@aws-sdk/lib-storage"; 3 | import axios from 'axios'; 4 | import crypto from 'crypto'; 5 | 6 | const s3 = new S3Client({ apiVersion: '2012-11-05', region: process.env.S3_BUCKET_REGION }); 7 | 8 | export const handler = async (event) => { 9 | try { 10 | console.log(`Incoming event: ${JSON.stringify(event)}`); 11 | const eventBody = JSON.parse(event.body); 12 | 13 | // Normalize headers 14 | const normalizedHeaders = normalizeObject(event.headers); 15 | 16 | // Respond to test event 17 | if ('x-event-key' in normalizedHeaders && normalizedHeaders['x-event-key'] === 'diagnostics:ping') { 18 | return responseToApiGw(200, 'Webhook configured successfully'); 19 | } 20 | 21 | // Validate message signature 22 | if (!(checkSignature(process.env.BITBUCKET_SECRET, normalizedHeaders['x-hub-signature'], event.body))) { 23 | console.log('Invalid webhook message signature'); 24 | return responseToApiGw(401, 'Signature is not valid'); 25 | } 26 | console.log('Signature validated successfully'); 27 | 28 | if (!(eventBody.changes[0].ref.type === 'BRANCH')) { 29 | console.log('Invalid event type'); 30 | throw new Error('Invalid event type'); 31 | } 32 | 33 | const repoConfig = { 34 | serverUrl: process.env.BITBUCKET_SERVER_URL, 35 | projectName: eventBody.repository.project.key, 36 | repoName: eventBody.repository.name, 37 | branch: eventBody.changes[0].ref.displayId, 38 | token: process.env.BITBUCKET_TOKEN 39 | }; 40 | 41 | let proxy; 42 | if (process.env.WEBPROXY_HOST && process.env.WEBPROXY_PORT) { 43 | proxy = { 44 | host: process.env.WEBPROXY_HOST, 45 | port: process.env.WEBPROXY_PORT 46 | }; 47 | } 48 | 49 | // Download the repository package from Bitbucket Server 50 | const file = await downloadFile(repoConfig); 51 | 52 | let params = { 53 | Bucket: process.env.S3BUCKET, 54 | Key: `${repoConfig.projectName}/${repoConfig.repoName}/${repoConfig.branch}.zip`, 55 | Body: file 56 | }; 57 | 58 | const parallelUploads3 = new Upload({ 59 | client: s3, 60 | params: params, 61 | }); 62 | 63 | parallelUploads3.on("httpUploadProgress", (progress) => { 64 | 65 | }); 66 | 67 | await parallelUploads3.done(); 68 | 69 | return responseToApiGw(200, 'success'); 70 | } 71 | catch (err) { 72 | console.log('Exiting with error', err); 73 | return responseToApiGw(500, 'Some weird thing happened'); 74 | } 75 | }; 76 | 77 | /** 78 | * Convert an object keys to lowercase 79 | * @param {object} request - this is a object to convert the keys to lowercase 80 | * @returns {object} - return a new object with keys in lower case 81 | */ 82 | function normalizeObject(inputObject) { 83 | console.log('info', '>>> normalizeObject()'); 84 | 85 | const requestKeys = Object.keys(inputObject); 86 | 87 | let outputObject = {}; 88 | for (let i = 0; i < requestKeys.length; i++) { 89 | outputObject[requestKeys[i].toLowerCase()] = inputObject[requestKeys[i]]; 90 | } 91 | 92 | console.log('info', '<<< normalizeObject()'); 93 | return outputObject; 94 | } 95 | 96 | /** 97 | * Download the repository content as a zip file 98 | * @param {object} repoConfig - this is a object containing the config for the repository 99 | * @param {object} proxy - this is a object containing the web proxy configuration 100 | * @returns {stream} - return a stream containing the repository zip file 101 | */ 102 | async function downloadFile(repoConfig, proxy) { 103 | console.log('info', '>>> downloadFile()'); 104 | const params = { 105 | method: 'get', 106 | responseType: 'stream', 107 | baseURL: repoConfig.serverUrl, 108 | url: `/rest/api/latest/projects/${repoConfig.projectName}/repos/${repoConfig.repoName}/archive?at=refs/heads/${repoConfig.branch}&format=zip`, 109 | headers: { 110 | Authorization: `Bearer ${repoConfig.token}` 111 | } 112 | }; 113 | 114 | try { 115 | const resp = await axios.request(params); 116 | console.log('info', '<<< downloadFile()'); 117 | return resp.data; 118 | } 119 | catch (err) { 120 | console.log('error', err); 121 | throw new Error(err); 122 | } 123 | } 124 | 125 | /** 126 | * Check BitBucket Server Signature 127 | * @param {string} signingSecret - this is the signing secret for the BitBucket Server webhook 128 | * @param {string} signature - this is the signatured applied by BitBucket to the message 129 | * @param {object} body - this is the message body 130 | * @returns {boolean} - return true or false 131 | */ 132 | function checkSignature(signingSecret, signature, body) { 133 | console.log('info', '>>> signingSecret()'); 134 | const hash = crypto.createHmac('sha256', signingSecret).update(body).digest('hex'); 135 | 136 | const signatureHash = signature.split('='); 137 | if (signatureHash[1] === hash) { 138 | console.log('info', '<<< signingSecret()'); 139 | return true; 140 | } 141 | 142 | console.log('info', '<<< signingSecret()'); 143 | return false; 144 | } 145 | 146 | /** 147 | * Generate a response for API Gateway 148 | * @param {string} statusCode - HTTP status code to return 149 | * @param {string} detail - this is message detail to return 150 | * @returns {object} - return the formatted response object 151 | */ 152 | function responseToApiGw(statusCode, detail) { 153 | if (!statusCode) { 154 | throw new TypeError('responseToApiGw() expects at least argument statusCode'); 155 | } 156 | if (statusCode !== '200' && !detail) { 157 | throw new TypeError('responseToApiGw() expects at least arguments statusCode and detail'); 158 | } 159 | 160 | let body = {}; 161 | if (statusCode === '200' && detail) { 162 | body = { 163 | statusCode: statusCode, 164 | message: detail 165 | }; 166 | } else if (statusCode === '200' && !detail) { 167 | body = { 168 | statusCode: statusCode 169 | }; 170 | } else { 171 | body = { 172 | statusCode: statusCode, 173 | fault: detail 174 | }; 175 | } 176 | let response = { 177 | statusCode: statusCode, 178 | body: JSON.stringify(body), 179 | headers: { 180 | 'Content-Type': 'application/json', 181 | 'Access-Control-Allow-Origin': '*', 182 | 'Access-Control-Allow-Methods': 'POST, GET', 183 | 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' 184 | } 185 | }; 186 | return response; 187 | } -------------------------------------------------------------------------------- /code/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awscodepipeline-bitbucket-integration", 3 | "version": "0.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "awscodepipeline-bitbucket-integration", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.6.7" 13 | } 14 | }, 15 | "node_modules/asynckit": { 16 | "version": "0.4.0", 17 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 18 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 19 | }, 20 | "node_modules/axios": { 21 | "version": "1.7.2", 22 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", 23 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", 24 | "dependencies": { 25 | "follow-redirects": "^1.15.6", 26 | "form-data": "^4.0.0", 27 | "proxy-from-env": "^1.1.0" 28 | } 29 | }, 30 | "node_modules/combined-stream": { 31 | "version": "1.0.8", 32 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 33 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 34 | "dependencies": { 35 | "delayed-stream": "~1.0.0" 36 | }, 37 | "engines": { 38 | "node": ">= 0.8" 39 | } 40 | }, 41 | "node_modules/delayed-stream": { 42 | "version": "1.0.0", 43 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 44 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 45 | "engines": { 46 | "node": ">=0.4.0" 47 | } 48 | }, 49 | "node_modules/follow-redirects": { 50 | "version": "1.15.6", 51 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 52 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 53 | "funding": [ 54 | { 55 | "type": "individual", 56 | "url": "https://github.com/sponsors/RubenVerborgh" 57 | } 58 | ], 59 | "engines": { 60 | "node": ">=4.0" 61 | }, 62 | "peerDependenciesMeta": { 63 | "debug": { 64 | "optional": true 65 | } 66 | } 67 | }, 68 | "node_modules/form-data": { 69 | "version": "4.0.0", 70 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 71 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 72 | "dependencies": { 73 | "asynckit": "^0.4.0", 74 | "combined-stream": "^1.0.8", 75 | "mime-types": "^2.1.12" 76 | }, 77 | "engines": { 78 | "node": ">= 6" 79 | } 80 | }, 81 | "node_modules/mime-db": { 82 | "version": "1.52.0", 83 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 84 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 85 | "engines": { 86 | "node": ">= 0.6" 87 | } 88 | }, 89 | "node_modules/mime-types": { 90 | "version": "2.1.35", 91 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 92 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 93 | "dependencies": { 94 | "mime-db": "1.52.0" 95 | }, 96 | "engines": { 97 | "node": ">= 0.6" 98 | } 99 | }, 100 | "node_modules/proxy-from-env": { 101 | "version": "1.1.0", 102 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 103 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 104 | } 105 | }, 106 | "dependencies": { 107 | "asynckit": { 108 | "version": "0.4.0", 109 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 110 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 111 | }, 112 | "axios": { 113 | "version": "1.7.2", 114 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", 115 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", 116 | "requires": { 117 | "follow-redirects": "^1.15.6", 118 | "form-data": "^4.0.0", 119 | "proxy-from-env": "^1.1.0" 120 | } 121 | }, 122 | "combined-stream": { 123 | "version": "1.0.8", 124 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 125 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 126 | "requires": { 127 | "delayed-stream": "~1.0.0" 128 | } 129 | }, 130 | "delayed-stream": { 131 | "version": "1.0.0", 132 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 133 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 134 | }, 135 | "follow-redirects": { 136 | "version": "1.15.6", 137 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 138 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" 139 | }, 140 | "form-data": { 141 | "version": "4.0.0", 142 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 143 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 144 | "requires": { 145 | "asynckit": "^0.4.0", 146 | "combined-stream": "^1.0.8", 147 | "mime-types": "^2.1.12" 148 | } 149 | }, 150 | "mime-db": { 151 | "version": "1.52.0", 152 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 153 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 154 | }, 155 | "mime-types": { 156 | "version": "2.1.35", 157 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 158 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 159 | "requires": { 160 | "mime-db": "1.52.0" 161 | } 162 | }, 163 | "proxy-from-env": { 164 | "version": "1.1.0", 165 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 166 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awscodepipeline-bitbucket-integration", 3 | "version": "0.1.0", 4 | "description": "Integrates AWS CodePipeline with a Bitbucket Server", 5 | "main": "index.js", 6 | "type": "module", 7 | "author": "Alex Rosa ", 8 | "license": "MIT", 9 | "dependencies": { 10 | "axios": "^1.6.7" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /code/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | 6 | "asynckit@^0.4.0": 7 | "integrity" "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 8 | "resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" 9 | "version" "0.4.0" 10 | 11 | "axios@^1.6.7": 12 | "integrity" "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==" 13 | "resolved" "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz" 14 | "version" "1.7.2" 15 | dependencies: 16 | "follow-redirects" "^1.15.6" 17 | "form-data" "^4.0.0" 18 | "proxy-from-env" "^1.1.0" 19 | 20 | "combined-stream@^1.0.8": 21 | "integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==" 22 | "resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" 23 | "version" "1.0.8" 24 | dependencies: 25 | "delayed-stream" "~1.0.0" 26 | 27 | "delayed-stream@~1.0.0": 28 | "integrity" "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 29 | "resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" 30 | "version" "1.0.0" 31 | 32 | "follow-redirects@^1.15.6": 33 | "integrity" "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" 34 | "resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" 35 | "version" "1.15.6" 36 | 37 | "form-data@^4.0.0": 38 | "integrity" "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==" 39 | "resolved" "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" 40 | "version" "4.0.0" 41 | dependencies: 42 | "asynckit" "^0.4.0" 43 | "combined-stream" "^1.0.8" 44 | "mime-types" "^2.1.12" 45 | 46 | "mime-db@1.52.0": 47 | "integrity" "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 48 | "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" 49 | "version" "1.52.0" 50 | 51 | "mime-types@^2.1.12": 52 | "integrity" "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==" 53 | "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" 54 | "version" "2.1.35" 55 | dependencies: 56 | "mime-db" "1.52.0" 57 | 58 | "proxy-from-env@^1.1.0": 59 | "integrity" "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 60 | "resolved" "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" 61 | "version" "1.1.0" 62 | -------------------------------------------------------------------------------- /infra/infra.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | 4 | Description: CodePipeline integration with the Bitbucket Server 5 | 6 | Parameters: 7 | BitbucketSecret: 8 | Type: String 9 | Description: Bitbucket webhook secret used to sign webhook events. You should define the secret and use the same value here and in the Bitbucket server webhook. 10 | NoEcho: true 11 | BitbucketServerUrl: 12 | Type: String 13 | Description: URL of your Bitbucket Server e.g. http://server:port 14 | BitbucketToken: 15 | Type: String 16 | Description: Personal token generated to access the repositories 17 | NoEcho: true 18 | EndpointType: 19 | Type: String 20 | Description: Select the type of endpoint to integrate with the Lambda Function 21 | AllowedValues: 22 | - API Gateway 23 | - Application Load Balancer 24 | LBCIDR: 25 | Type: String 26 | Description: CIDR allowed to communicate with the Load Balancer. It should allow the Bitbucket server IP address. Leave it blank if you are using the API Gateway endpoint type. 27 | LBSubnets: 28 | Type: List 29 | Description: Subnets where the Application Load Balancer run. Leave it blank if you are using the API Gateway endpoint type. 30 | LBSSLCertificateArn: 31 | Type: String 32 | Description: SSL Certificate to associate with the Application Load Balancer. Leave it blank if you are using the API Gateway endpoint type. 33 | LambdaSubnets: 34 | Type: List 35 | Description: Subnets where the Lambda Function run 36 | S3BucketCodePipelineName: 37 | Type: String 38 | Description: S3 bucket name to store the Bitbucket repository content 39 | AllowedPattern: '^((?!xn--)(?!.*-s3alias$)[a-z0-9][a-z0-9-]{1,61}[a-z0-9])$' 40 | ConstraintDescription: This field should contain only lower case characters 41 | S3BucketRegion: 42 | Type: String 43 | Description: S3 bucket region 44 | LambdaS3Bucket: 45 | Type: String 46 | Description: S3 bucket name which stores code for Lambda function 47 | AllowedPattern: ^((?!xn--)(?!.*-s3alias$)[a-z0-9][a-z0-9-]{1,61}[a-z0-9])$ 48 | ConstraintDescription: This field should contain only lower case characters 49 | VPCID: 50 | Type: AWS::EC2::VPC::Id 51 | Description: VPC ID where the Application Load Balancer and the Lambda function run 52 | WebProxyHost: 53 | Type: String 54 | Description: Hostname of your Proxy server used by the Lambda Function to access the Bitbucket server. If you don't need a web proxy leave it blank. e.g. myproxy.mydomain.com 55 | WebProxyPort: 56 | Type: String 57 | Description: Port of your Proxy server used by the Lambda Function to access the Bitbucket server. If you don't need a web proxy leave it blank. e.g. 8080 58 | 59 | Conditions: 60 | EndpointTypeALB: !Equals [ !Ref EndpointType, 'Application Load Balancer' ] 61 | EndpointTypeAPIGW: !Equals [ !Ref EndpointType, 'API Gateway' ] 62 | 63 | Resources: 64 | S3BucketCodePipeline: 65 | Type: AWS::S3::Bucket 66 | Properties: 67 | BucketName: !Ref S3BucketCodePipelineName 68 | VersioningConfiguration: 69 | Status: Enabled 70 | BucketEncryption: 71 | ServerSideEncryptionConfiguration: 72 | - ServerSideEncryptionByDefault: 73 | SSEAlgorithm: AES256 74 | 75 | KMSKey: 76 | Type: AWS::KMS::Key 77 | Properties: 78 | Description: "CMK used by the Lambda Function to encrypt the environment variables" 79 | KeyPolicy: 80 | Version: "2012-10-17" 81 | Id: "root" 82 | Statement: 83 | - 84 | Sid: "Enable IAM User Permissions" 85 | Effect: "Allow" 86 | Principal: 87 | AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root" 88 | Action: "kms:*" 89 | Resource: "*" 90 | 91 | IamPolicyLambdaFunction: 92 | Type: AWS::IAM::ManagedPolicy 93 | Properties: 94 | Description: AWS CodePipeline integration with BitBucket Server 95 | ManagedPolicyName: CodePipeline-Bitbucket-Integration 96 | PolicyDocument: 97 | Version: '2012-10-17' 98 | Statement: 99 | - Effect: Allow 100 | Action: 101 | - s3:PutObject 102 | Resource: 103 | - !Sub 'arn:aws:s3:::${S3BucketCodePipeline}' 104 | - !Sub 'arn:aws:s3:::${S3BucketCodePipeline}/*' 105 | - Effect: Allow 106 | Action: 107 | - kms:decrypt 108 | Resource: 109 | - !GetAtt KMSKey.Arn 110 | 111 | IamRoleLambdaFunction: 112 | Type: AWS::IAM::Role 113 | Properties: 114 | AssumeRolePolicyDocument: 115 | Version: '2012-10-17' 116 | Statement: 117 | - Effect: Allow 118 | Principal: 119 | Service: lambda.amazonaws.com 120 | Action: sts:AssumeRole 121 | ManagedPolicyArns: 122 | - !Ref 'IamPolicyLambdaFunction' 123 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 124 | RoleName: CodePipeline-Bitbucket-Integration 125 | 126 | SgLambdaFunction: 127 | Type: AWS::EC2::SecurityGroup 128 | Properties: 129 | GroupDescription: SG used by the Bitbucket Integration Lambda Function 130 | GroupName: CodePipeline-Bitbucket-Integration-Lambda 131 | VpcId: !Ref VPCID 132 | 133 | SgAlb: 134 | Type: AWS::EC2::SecurityGroup 135 | Condition: EndpointTypeALB 136 | Properties: 137 | GroupDescription: SG used by the Bitbucket Integration ALB 138 | GroupName: CodePipeline-Bitbucket-Integration-ALB 139 | SecurityGroupIngress: 140 | - CidrIp: !Ref LBCIDR 141 | Description: Range of IP allowed to connect to the Load Balancer 142 | FromPort: 443 143 | ToPort: 443 144 | IpProtocol: tcp 145 | VpcId: !Ref VPCID 146 | 147 | LambdaFunction: 148 | Type: AWS::Lambda::Function 149 | Properties: 150 | FunctionName: CodePipeline-Bitbucket-Integration 151 | Environment: 152 | Variables: 153 | BITBUCKET_SERVER_URL: 154 | !Ref BitbucketServerUrl 155 | BITBUCKET_TOKEN: 156 | !Ref BitbucketToken 157 | BITBUCKET_SECRET: 158 | !Ref BitbucketSecret 159 | S3BUCKET: 160 | !Ref S3BucketCodePipeline 161 | WEBPROXY_HOST: 162 | !Ref WebProxyHost 163 | WEBPROXY_PORT: 164 | !Ref WebProxyPort 165 | S3_BUCKET_REGION: 166 | !Ref: S3BucketRegion 167 | Handler: index.handler 168 | KmsKeyArn: !GetAtt KMSKey.Arn 169 | Role: 170 | !GetAtt IamRoleLambdaFunction.Arn 171 | Code: ../code 172 | Runtime: nodejs20.x 173 | Timeout: '30' 174 | VpcConfig: 175 | SecurityGroupIds: 176 | - Ref: SgLambdaFunction 177 | SubnetIds: !Ref LambdaSubnets 178 | 179 | LambdaPermissionAlb: 180 | Type: AWS::Lambda::Permission 181 | Condition: EndpointTypeALB 182 | Properties: 183 | Action: lambda:InvokeFunction 184 | FunctionName: 185 | !Ref LambdaFunction 186 | Principal: elasticloadbalancing.amazonaws.com 187 | 188 | LambdaPermissionAPIGw: 189 | Type: AWS::Lambda::Permission 190 | Condition: EndpointTypeAPIGW 191 | Properties: 192 | Action: lambda:InvokeFunction 193 | FunctionName: 194 | !Ref LambdaFunction 195 | Principal: apigateway.amazonaws.com 196 | 197 | Alb: 198 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 199 | Condition: EndpointTypeALB 200 | Properties: 201 | Name: cp-bitbucket-int 202 | Scheme: internet-facing 203 | SecurityGroups: 204 | - Ref: SgAlb 205 | Subnets: !Ref LBSubnets 206 | IpAddressType: ipv4 207 | 208 | AlbListener443: 209 | Type: AWS::ElasticLoadBalancingV2::Listener 210 | Condition: EndpointTypeALB 211 | Properties: 212 | DefaultActions: 213 | - TargetGroupArn: 214 | Ref: TargetGroup 215 | Type: forward 216 | LoadBalancerArn: 217 | Ref: Alb 218 | Port: 443 219 | Protocol: HTTPS 220 | Certificates: 221 | - CertificateArn: 222 | Ref: LBSSLCertificateArn 223 | 224 | TargetGroup: 225 | DependsOn: LambdaPermissionAlb 226 | Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' 227 | Condition: EndpointTypeALB 228 | Properties: 229 | TargetType: lambda 230 | Name: cp-bitbucket-int 231 | Targets: 232 | - Id: !GetAtt LambdaFunction.Arn 233 | 234 | RestApi: 235 | Type: AWS::ApiGateway::RestApi 236 | Condition: EndpointTypeAPIGW 237 | Properties: 238 | Description: API used by the AWS CodePipeline integration with the Bitbucket Server 239 | EndpointConfiguration: 240 | Types: 241 | - REGIONAL 242 | Name: CodePipeline-Bitbucket-Integration 243 | 244 | RestApiMethod: 245 | Type: AWS::ApiGateway::Method 246 | Condition: EndpointTypeAPIGW 247 | Properties: 248 | AuthorizationType: NONE 249 | HttpMethod: POST 250 | Integration: 251 | IntegrationHttpMethod: POST 252 | Type: AWS_PROXY 253 | Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations' 254 | ResourceId: !GetAtt 255 | - RestApi 256 | - RootResourceId 257 | RestApiId: !Ref RestApi 258 | 259 | RestApiDeployment: 260 | Type: AWS::ApiGateway::Deployment 261 | Condition: EndpointTypeAPIGW 262 | DependsOn: RestApiMethod 263 | Properties: 264 | Description: Stage Deployment 265 | RestApiId: !Ref RestApi 266 | StageName: prod 267 | 268 | Outputs: 269 | EndpointUrlAlb: 270 | Condition: EndpointTypeALB 271 | Value: 272 | Fn::GetAtt: 273 | - Alb 274 | - DNSName 275 | EndpointUrlApiGw: 276 | Condition: EndpointTypeAPIGW 277 | Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/prod/" 278 | 279 | -------------------------------------------------------------------------------- /infra/parameters.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ParameterKey": "BitbucketSecret", 4 | "ParameterValue": "" 5 | }, 6 | { 7 | "ParameterKey": "BitbucketServerUrl", 8 | "ParameterValue": "" 9 | }, 10 | { 11 | "ParameterKey": "BitbucketToken", 12 | "ParameterValue": "" 13 | }, 14 | { 15 | "ParameterKey": "EndpointType", 16 | "ParameterValue": "" 17 | }, 18 | { 19 | "ParameterKey": "LBSubnets", 20 | "ParameterValue": "" 21 | }, 22 | { 23 | "ParameterKey": "LBSSLCertificateArn", 24 | "ParameterValue": "" 25 | }, 26 | { 27 | "ParameterKey": "LBCIDR", 28 | "ParameterValue": "" 29 | }, 30 | { 31 | "ParameterKey": "LambdaSubnets", 32 | "ParameterValue": "" 33 | }, 34 | { 35 | "ParameterKey": "S3BucketCodePipelineName", 36 | "ParameterValue": "" 37 | }, 38 | { 39 | "ParameterKey": "VPCID", 40 | "ParameterValue": "" 41 | }, 42 | { 43 | "ParameterKey": "WebProxyHost", 44 | "ParameterValue": "" 45 | }, 46 | { 47 | "ParameterKey": "WebProxyPort", 48 | "ParameterValue": "" 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /infra/params.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ParameterKey": "BitbucketSecret", 4 | "ParameterValue": "mypassword" 5 | }, 6 | { 7 | "ParameterKey": "BitbucketServerUrl", 8 | "ParameterValue": "http://1.2.3.4:7990" 9 | }, 10 | { 11 | "ParameterKey": "BitbucketToken", 12 | "ParameterValue": "aaaaaaaaaaaaaaaaaaaaaaaaa" 13 | }, 14 | { 15 | "ParameterKey": "EndpointType", 16 | "ParameterValue": "API Gateway" 17 | }, 18 | { 19 | "ParameterKey": "LBSubnets", 20 | "ParameterValue": "subnet-1234567,subnet-2345678" 21 | }, 22 | { 23 | "ParameterKey": "LBSSLCertificateArn", 24 | "ParameterValue": "" 25 | }, 26 | { 27 | "ParameterKey": "LBCIDR", 28 | "ParameterValue": "1.2.3.4/32" 29 | }, 30 | { 31 | "ParameterKey": "LambdaSubnets", 32 | "ParameterValue": "subnet-1234567,subnet-2345678" 33 | }, 34 | { 35 | "ParameterKey": "S3BucketCodePipelineName", 36 | "ParameterValue": "awscpbitbucketintegrationb" 37 | }, 38 | { 39 | "ParameterKey": "VPCID", 40 | "ParameterValue": "vpc-12345678" 41 | }, 42 | { 43 | "ParameterKey": "WebProxyHost", 44 | "ParameterValue": "" 45 | }, 46 | { 47 | "ParameterKey": "WebProxyPort", 48 | "ParameterValue": "" 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-codepipeline-bitbucket-integration", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: CodePipeline integration with the Bitbucket Server 3 | Parameters: 4 | BitbucketSecret: 5 | Type: String 6 | Description: Bitbucket webhook secret used to sign webhook events. You should 7 | define the secret and use the same value here and in the Bitbucket server webhook. 8 | NoEcho: true 9 | BitbucketServerUrl: 10 | Type: String 11 | Description: URL of your Bitbucket Server e.g. http://server:port 12 | BitbucketToken: 13 | Type: String 14 | Description: Personal token generated to access the repositories 15 | NoEcho: true 16 | EndpointType: 17 | Type: String 18 | Description: Select the type of endpoint to integrate with the Lambda Function 19 | AllowedValues: 20 | - API Gateway 21 | - Application Load Balancer 22 | LBCIDR: 23 | Type: String 24 | Description: CIDR allowed to communicate with the Load Balancer. It should allow 25 | the Bitbucket server IP address. Leave it blank if you are using the API Gateway 26 | endpoint type. 27 | LBSubnets: 28 | Type: List 29 | Description: Subnets where the Application Load Balancer run. Leave it blank if 30 | you are using the API Gateway endpoint type. 31 | LBSSLCertificateArn: 32 | Type: String 33 | Description: SSL Certificate to associate with the Application Load Balancer. 34 | Leave it blank if you are using the API Gateway endpoint type. 35 | LambdaSubnets: 36 | Type: List 37 | Description: Subnets where the Lambda Function run 38 | S3BucketCodePipelineName: 39 | Type: String 40 | Description: S3 bucket name to store the Bitbucket repository content 41 | AllowedPattern: ^((?!xn--)(?!.*-s3alias$)[a-z0-9][a-z0-9-]{1,61}[a-z0-9])$ 42 | ConstraintDescription: This field should contain only lower case characters 43 | S3BucketRegion: 44 | Type: String 45 | Description: S3 bucket region 46 | LambdaS3Bucket: 47 | Type: String 48 | Description: S3 bucket name which stores code for Lambda function 49 | AllowedPattern: ^((?!xn--)(?!.*-s3alias$)[a-z0-9][a-z0-9-]{1,61}[a-z0-9])$ 50 | ConstraintDescription: This field should contain only lower case characters 51 | VPCID: 52 | Type: AWS::EC2::VPC::Id 53 | Description: VPC ID where the Application Load Balancer and the Lambda function 54 | run 55 | WebProxyHost: 56 | Type: String 57 | Description: Hostname of your Proxy server used by the Lambda Function to access 58 | the Bitbucket server. If you don't need a web proxy leave it blank. e.g. myproxy.mydomain.com 59 | WebProxyPort: 60 | Type: String 61 | Description: Port of your Proxy server used by the Lambda Function to access the 62 | Bitbucket server. If you don't need a web proxy leave it blank. e.g. 8080 63 | Conditions: 64 | EndpointTypeALB: 65 | Fn::Equals: 66 | - Ref: EndpointType 67 | - Application Load Balancer 68 | EndpointTypeAPIGW: 69 | Fn::Equals: 70 | - Ref: EndpointType 71 | - API Gateway 72 | Resources: 73 | S3BucketCodePipeline: 74 | Type: AWS::S3::Bucket 75 | Properties: 76 | BucketName: 77 | Ref: S3BucketCodePipelineName 78 | VersioningConfiguration: 79 | Status: Enabled 80 | BucketEncryption: 81 | ServerSideEncryptionConfiguration: 82 | - ServerSideEncryptionByDefault: 83 | SSEAlgorithm: AES256 84 | KMSKey: 85 | Type: AWS::KMS::Key 86 | Properties: 87 | Description: CMK used by the Lambda Function to encrypt the environment variables 88 | KeyPolicy: 89 | Version: '2012-10-17' 90 | Id: root 91 | Statement: 92 | - Sid: Enable IAM User Permissions 93 | Effect: Allow 94 | Principal: 95 | AWS: 96 | Fn::Sub: arn:aws:iam::${AWS::AccountId}:root 97 | Action: kms:* 98 | Resource: '*' 99 | IamPolicyLambdaFunction: 100 | Type: AWS::IAM::ManagedPolicy 101 | Properties: 102 | Description: AWS CodePipeline integration with BitBucket Server 103 | ManagedPolicyName: CodePipeline-Bitbucket-Integration 104 | PolicyDocument: 105 | Version: '2012-10-17' 106 | Statement: 107 | - Effect: Allow 108 | Action: 109 | - s3:PutObject 110 | Resource: 111 | - Fn::Sub: arn:aws:s3:::${S3BucketCodePipeline} 112 | - Fn::Sub: arn:aws:s3:::${S3BucketCodePipeline}/* 113 | - Effect: Allow 114 | Action: 115 | - kms:decrypt 116 | Resource: 117 | - Fn::GetAtt: 118 | - KMSKey 119 | - Arn 120 | IamRoleLambdaFunction: 121 | Type: AWS::IAM::Role 122 | Properties: 123 | AssumeRolePolicyDocument: 124 | Version: '2012-10-17' 125 | Statement: 126 | - Effect: Allow 127 | Principal: 128 | Service: lambda.amazonaws.com 129 | Action: sts:AssumeRole 130 | ManagedPolicyArns: 131 | - Ref: IamPolicyLambdaFunction 132 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 133 | RoleName: CodePipeline-Bitbucket-Integration 134 | SgLambdaFunction: 135 | Type: AWS::EC2::SecurityGroup 136 | Properties: 137 | GroupDescription: SG used by the Bitbucket Integration Lambda Function 138 | GroupName: CodePipeline-Bitbucket-Integration-Lambda 139 | VpcId: 140 | Ref: VPCID 141 | SgAlb: 142 | Type: AWS::EC2::SecurityGroup 143 | Condition: EndpointTypeALB 144 | Properties: 145 | GroupDescription: SG used by the Bitbucket Integration ALB 146 | GroupName: CodePipeline-Bitbucket-Integration-ALB 147 | SecurityGroupIngress: 148 | - CidrIp: 149 | Ref: LBCIDR 150 | Description: Range of IP allowed to connect to the Load Balancer 151 | FromPort: 443 152 | ToPort: 443 153 | IpProtocol: tcp 154 | VpcId: 155 | Ref: VPCID 156 | LambdaFunction: 157 | Type: AWS::Lambda::Function 158 | Properties: 159 | FunctionName: CodePipeline-Bitbucket-Integration 160 | Environment: 161 | Variables: 162 | BITBUCKET_SERVER_URL: 163 | Ref: BitbucketServerUrl 164 | BITBUCKET_TOKEN: 165 | Ref: BitbucketToken 166 | BITBUCKET_SECRET: 167 | Ref: BitbucketSecret 168 | S3BUCKET: 169 | Ref: S3BucketCodePipeline 170 | WEBPROXY_HOST: 171 | Ref: WebProxyHost 172 | WEBPROXY_PORT: 173 | Ref: WebProxyPort 174 | S3_BUCKET_REGION: 175 | Ref: S3BucketRegion 176 | Handler: index.handler 177 | KmsKeyArn: 178 | Fn::GetAtt: 179 | - KMSKey 180 | - Arn 181 | Role: 182 | Fn::GetAtt: 183 | - IamRoleLambdaFunction 184 | - Arn 185 | Code: 186 | S3Bucket: 187 | Ref: LambdaS3Bucket 188 | S3Key: code.zip 189 | Runtime: nodejs20.x 190 | Timeout: '30' 191 | VpcConfig: 192 | SecurityGroupIds: 193 | - Ref: SgLambdaFunction 194 | SubnetIds: 195 | Ref: LambdaSubnets 196 | LambdaPermissionAlb: 197 | Type: AWS::Lambda::Permission 198 | Condition: EndpointTypeALB 199 | Properties: 200 | Action: lambda:InvokeFunction 201 | FunctionName: 202 | Ref: LambdaFunction 203 | Principal: elasticloadbalancing.amazonaws.com 204 | LambdaPermissionAPIGw: 205 | Type: AWS::Lambda::Permission 206 | Condition: EndpointTypeAPIGW 207 | Properties: 208 | Action: lambda:InvokeFunction 209 | FunctionName: 210 | Ref: LambdaFunction 211 | Principal: apigateway.amazonaws.com 212 | Alb: 213 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 214 | Condition: EndpointTypeALB 215 | Properties: 216 | Name: cp-bitbucket-int 217 | Scheme: internet-facing 218 | SecurityGroups: 219 | - Ref: SgAlb 220 | Subnets: 221 | Ref: LBSubnets 222 | IpAddressType: ipv4 223 | AlbListener443: 224 | Type: AWS::ElasticLoadBalancingV2::Listener 225 | Condition: EndpointTypeALB 226 | Properties: 227 | DefaultActions: 228 | - TargetGroupArn: 229 | Ref: TargetGroup 230 | Type: forward 231 | LoadBalancerArn: 232 | Ref: Alb 233 | Port: 443 234 | Protocol: HTTPS 235 | Certificates: 236 | - CertificateArn: 237 | Ref: LBSSLCertificateArn 238 | TargetGroup: 239 | DependsOn: LambdaPermissionAlb 240 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 241 | Condition: EndpointTypeALB 242 | Properties: 243 | TargetType: lambda 244 | Name: cp-bitbucket-int 245 | Targets: 246 | - Id: 247 | Fn::GetAtt: 248 | - LambdaFunction 249 | - Arn 250 | RestApi: 251 | Type: AWS::ApiGateway::RestApi 252 | Condition: EndpointTypeAPIGW 253 | Properties: 254 | Description: API used by the AWS CodePipeline integration with the Bitbucket 255 | Server 256 | EndpointConfiguration: 257 | Types: 258 | - REGIONAL 259 | Name: CodePipeline-Bitbucket-Integration 260 | RestApiMethod: 261 | Type: AWS::ApiGateway::Method 262 | Condition: EndpointTypeAPIGW 263 | Properties: 264 | AuthorizationType: NONE 265 | HttpMethod: POST 266 | Integration: 267 | IntegrationHttpMethod: POST 268 | Type: AWS_PROXY 269 | Uri: 270 | Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations 271 | ResourceId: 272 | Fn::GetAtt: 273 | - RestApi 274 | - RootResourceId 275 | RestApiId: 276 | Ref: RestApi 277 | RestApiDeployment: 278 | Type: AWS::ApiGateway::Deployment 279 | Condition: EndpointTypeAPIGW 280 | DependsOn: RestApiMethod 281 | Properties: 282 | Description: Stage Deployment 283 | RestApiId: 284 | Ref: RestApi 285 | StageName: prod 286 | Outputs: 287 | EndpointUrlAlb: 288 | Condition: EndpointTypeALB 289 | Value: 290 | Fn::GetAtt: 291 | - Alb 292 | - DNSName 293 | EndpointUrlApiGw: 294 | Condition: EndpointTypeAPIGW 295 | Value: 296 | Fn::Sub: https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/prod/ 297 | --------------------------------------------------------------------------------