├── .github
└── workflows
│ ├── ci.yml
│ ├── test.yml
│ └── wiki.yml
├── .gitignore
├── Contributing.md
├── Licence
├── README.md
├── ansible
├── ansible.cfg
├── boundary.yml
├── cleanup.yml
├── handlers
│ ├── container_healthcheck.yml
│ ├── fail_task.yml
│ └── service_healthcheck.yml
├── host_vars
│ ├── hashicorp
│ └── localhost
├── inventory
│ └── inventory.ini
├── playbook.yml
├── roles
│ ├── boundary
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ ├── boundary_iac.yml
│ │ │ ├── deploy.yml
│ │ │ ├── init_credentials.yml
│ │ │ └── main.yml
│ │ └── vars
│ │ │ └── main.yml
│ ├── cleanup
│ │ ├── tasks
│ │ │ ├── main.yml
│ │ │ ├── secrets_file.yml
│ │ │ └── tokens.yml
│ │ └── vars
│ │ │ └── main.yml
│ ├── prepare_env
│ │ ├── tasks
│ │ │ ├── docker_images.yml
│ │ │ ├── files_and_directories.yml
│ │ │ └── main.yml
│ │ └── vars
│ │ │ └── main.yml
│ ├── provision
│ │ ├── default
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── vars
│ │ │ └── main.yml
│ ├── terraform
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ ├── cred_store_ssh.yml
│ │ │ ├── main.yml
│ │ │ ├── tfvars_token.yml
│ │ │ └── vault_iac.yml
│ │ └── vars
│ │ │ └── main.yml
│ └── vault
│ │ ├── defaults
│ │ └── main.yml
│ │ ├── tasks
│ │ ├── deploy.yml
│ │ ├── docker_pull_status.yml
│ │ ├── main.yml
│ │ └── secrets.yml
│ │ └── vars
│ │ └── main.yml
├── terraform.yml
└── utils
│ └── secrets.yml
├── artifacts
├── diagrams
│ ├── boundary.py
│ └── vault.py
├── wiki.md
└── wiki
│ ├── app.js
│ ├── index.html
│ └── style.css
├── boundary
├── config
│ └── boundary.hcl
└── terraform
│ ├── README.md
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ ├── terraform.tf
│ ├── terraform.tfvars.sample
│ └── variables.tf
├── deploy
├── boundary.yml
└── vault.yml
├── g
├── provision
└── specs.txt
├── requirements.txt
├── requirements.yml
├── scripts
├── cleanup.sh
├── deploy.sh
├── init.sh
├── install_services.sh
├── install_vagrant.sh
├── install_virtual_box.sh
├── linter.sh
├── pull-images.sh
└── tls-gen.sh
├── start.sh
├── vagrantfile
└── vault
├── config
└── vault.hcl
├── policy
└── kv-policy.hcl
└── terraform
├── auth.tf
├── main.tf
├── output.tf
├── policies.tf
├── policies
├── admin-policy.hcl
├── boundary-controller.hcl
├── boundary-transit.hcl
├── ssh.hcl
├── testapp.hcl
└── transit-admin.hcl
├── secrets.tf
├── terraform.tfvars.sample
└── variables.tf
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: BVSTACK CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | permissions:
10 | contents: write
11 | pull-requests: write
12 |
13 | jobs:
14 | ## build to be implemented
15 |
16 | release:
17 | if: github.event_name != 'pull_request'
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Bump version and push tag
21 | id: bump
22 | uses: mathieudutour/github-tag-action@a22cf08638b34d5badda920f9daf6e72c477b07b
23 | with:
24 | github_token: ${{ secrets.GITHUB_TOKEN }}
25 | default_bump: patch
26 |
27 | - name: Build Changelog
28 | id: github_release
29 | uses: mikepenz/release-changelog-builder-action@f3fc77b47b74e78971fffecb2102ae6eac9a44d6
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 | fromTag: ${{ steps.bump.outputs.previous_tag }}
33 | toTag: ${{ steps.bump.outputs.new_tag }}
34 |
35 | - name: Create Release
36 | uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
37 | with:
38 | body: ${{ steps.bump.outputs.changelog }}
39 | tag_name: ${{ steps.bump.outputs.new_tag }}
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: BVSTACK Test Pipeline
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - 'main'
7 | pull_request:
8 |
9 | env:
10 | VAULT_ADDR: ${{ vars.VAULT_ADDR }}
11 | VAULT_TOKEN: ${{ vars.VAULT_TOKEN }}
12 | BOUNDARY_ADDR: ${{ vars.BOUNDARY_ADDR }}
13 |
14 | jobs:
15 | lint-boundary-terraform:
16 | runs-on: ubuntu-latest
17 | defaults:
18 | run:
19 | working-directory: "boundary/terraform/"
20 | steps:
21 | - name: Checkout code
22 | uses: actions/checkout@v4
23 |
24 | - name: setup terraform cli
25 | uses: hashicorp/setup-terraform@v3
26 |
27 | - name: Terraform fmt
28 | id: fmt
29 | run: terraform fmt -check
30 | continue-on-error: true
31 |
32 | - name: Terraform Init
33 | id: init
34 | run: terraform init
35 |
36 | - name: Terraform Validate
37 | id: validate
38 | run: terraform validate -no-color
39 |
40 | - name: validate stdout
41 | run: echo "${{ steps.validate.outputs.stdout }}"
42 |
43 | - name: validate sterr
44 | run: echo "${{ steps.validate.outputs.stderr }}"
45 |
46 | - name: validate exitcode
47 | run: echo "${{ steps.validate.outputs.exitcode }}"
48 |
49 | lint-vault-terraform:
50 | runs-on: ubuntu-latest
51 | defaults:
52 | run:
53 | working-directory: "vault/terraform/"
54 | steps:
55 | - name: Checkout code
56 | uses: actions/checkout@v4
57 |
58 | - name: setup terraform cli
59 | uses: hashicorp/setup-terraform@v3
60 |
61 | - name: Terraform fmt
62 | id: fmt
63 | run: terraform fmt -check
64 | continue-on-error: true
65 |
66 | - name: Terraform Init
67 | id: init
68 | run: terraform init
69 |
70 | - name: Terraform Validate
71 | id: validate
72 | run: terraform validate -no-color
73 |
74 | - run: echo ${{ steps.plan.outputs.stdout }}
75 | - run: echo ${{ steps.plan.outputs.stderr }}
76 | - run: echo ${{ steps.plan.outputs.exitcode }}
77 |
78 | lint-ansible:
79 | runs-on: ubuntu-latest
80 | defaults:
81 | run:
82 | working-directory: ./scripts
83 | shell: bash
84 | steps:
85 | - name: Checkout code
86 | uses: actions/checkout@v4
87 |
88 | - name: setup python
89 | uses: actions/setup-python@v5
90 | with:
91 | python-version: '3.10'
92 | cache: 'pip'
93 |
94 | - name: install ansible
95 | run: |
96 | pip install -U pip
97 | pip install ansible wheel
98 |
99 | - name: check playbook and role's syntax
100 | run: bash linter.sh ansible
--------------------------------------------------------------------------------
/.github/workflows/wiki.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy Wiki to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches:
8 | - main
9 | paths:
10 | - 'artifacts/**'
11 |
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
17 | permissions:
18 | contents: read
19 | pages: write
20 | id-token: write
21 |
22 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
23 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
24 | concurrency:
25 | group: "pages"
26 | cancel-in-progress: false
27 |
28 | jobs:
29 | generate_diagrams:
30 | runs-on: ubuntu-latest
31 | defaults:
32 | run:
33 | working-directory: "artifacts/diagrams/"
34 | steps:
35 | - name: Checkout
36 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
37 |
38 | - name: setup python
39 | uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5
40 | with:
41 | python-version: '3.10'
42 | cache: 'pip'
43 |
44 | - name: install diagram as code library
45 | run: |
46 | pip install -U pip
47 | pip install diagrams
48 | sudo apt update
49 | sudo apt install graphviz
50 |
51 | - name: generate diagrams
52 | run: |
53 | python vault.py
54 | python boundary.py
55 | mv *.png ${{github.workspace}}/artifacts/wiki/
56 | ls ../wiki/
57 |
58 | - uses: actions/upload-artifact@v4
59 | with:
60 | name: diagrams
61 | path: ${{github.workspace}}/artifacts/wiki/
62 |
63 | deploy-wiki:
64 | needs: generate_diagrams
65 | environment:
66 | name: github-pages
67 | url: ${{ steps.deployment.outputs.page_url }}
68 | runs-on: ubuntu-latest
69 | steps:
70 | - name: Checkout
71 | uses: actions/checkout@v4
72 |
73 | - name : download diagram artifacts
74 | uses: actions/download-artifact@v4
75 | with:
76 | name: diagrams
77 | path: ${{github.workspace}}/artifacts/wiki
78 | merge-multiple: true
79 |
80 | - name: Setup Pages
81 | uses: actions/configure-pages@v5
82 | - name: Upload artifact
83 | uses: actions/upload-pages-artifact@v3
84 | with:
85 | # Upload entire repository
86 | path: './artifacts/wiki'
87 | - name: Deploy to GitHub Pages
88 | id: deployment
89 | uses: actions/deploy-pages@v4
90 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/**audit**
2 |
3 | # Local .terraform directories
4 | **/.terraform/*
5 |
6 | # .tfstate files
7 | *.tfstate
8 | *.tfstate.*
9 | *.terraform.lock.hcl*
10 |
11 | # Crash log files
12 | crash.log
13 | crash.*.log
14 |
15 | #### Exclude all .tfvars files, which are likely to contain sensitive data, such as
16 | #### password, private keys, and other secrets. These should not be part of version
17 | #### control as they are data points which are potentially sensitive and subject
18 | #### to change depending on the environment.
19 | *.tfvars
20 | *.tfvars.json
21 |
22 | # Ignore override files as they are usually used to override resources locally and so
23 | # are not checked in
24 | override.tf
25 | override.tf.json
26 | *_override.tf
27 | *_override.tf.json
28 |
29 | # Include override files you do wish to add to version control using negated pattern
30 | # !example_override.tf
31 |
32 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
33 | # example: *tfplan*
34 |
35 | # Ignore CLI configuration files
36 | .terraformrc
37 | terraform.rc
38 |
39 | architecture/
40 |
41 |
42 | **/venv
43 |
44 | *secret*.txt
45 | **log**.txt
46 |
47 | to-do
48 | to-research
49 | **local.yml
50 | .vagrant
51 | *VBox*.log
52 | **/bin/boundary**
53 | **test-play**
54 | virtualenv
55 |
56 | **boundary*creds*.txt
--------------------------------------------------------------------------------
/Contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines for HashiCorp Boundary and Vault Stack
2 |
3 | Thank you for considering contributing to the HashiCorp Boundary and Vault Stack project! Your contributions are invaluable to the project's improvement. To ensure smooth collaboration, please follow the guidelines outlined below.
4 |
5 | ## Table of Contents
6 | - [Getting Started](#getting-started)
7 | - [Types of Contributions](#types-of-contributions)
8 | - [Reporting Issues](#reporting-issues)
9 | - [Commit Messages](#commit-messages)
10 | - [Submitting Pull Requests](#submitting-pull-requests)
11 | - [Code Style and Best Practices](#code-style-and-best-practices)
12 | - [Guidelines for Specific Tasks](#guidelines-for-specific-tasks)
13 | - [Terraform](#terraform)
14 | - [Ansible](#ansible)
15 | - [Shell Scripting](#shell-scripting)
16 | - [CI/CD](#cicd)
17 | - [Communication](#communication)
18 | - [License](#license)
19 |
20 | ## Getting Started
21 |
22 | 1. **Fork the Repository**: Begin by forking the repository to your GitHub account.
23 |
24 | 2. **Clone the Repository**: Clone your forked repository to your local machine:
25 | ```bash
26 | git clone https://github.com/your-username/boundary-vault-stack.git
27 | cd boundary-vault-stack
28 | ```
29 |
30 | 3. **Set Up Your Environment**: Ensure you have the necessary dependencies installed as outlined in the [documentation](https://devopshobbies.github.io/boundary-vault-stack/).
31 |
32 | 4. **Review the Documentation**: Familiarize yourself with the project by thoroughly reading the [documentation](https://devopshobbies.github.io/boundary-vault-stack/) and reviewing the [automation workflow diagram](https://linktw.in/PloXtt).
33 |
34 | ## Types of Contributions
35 |
36 | ### Reporting Issues
37 |
38 | If you encounter any bugs, errors, or have suggestions for improvements:
39 |
40 | - **Search Existing Issues**: Before submitting a new issue, check if it has already been reported.
41 | - **Create a New Issue**: If it’s a new issue, provide detailed information such as steps to reproduce, expected vs. actual results, and any relevant screenshots or logs.
42 | - **Link to Related Tasks**: If your issue relates to any of the [TODOs](https://github.com/devopshobbies/boundary-vault-stack/tree/main/#to-do), reference the corresponding task.
43 |
44 | ### Commit Messages
45 |
46 | **Use Conventional Commits**: The project follows [semantic versioning](https://semver.org/) to ensure proper releases. Start your commits with a prefix such as `fix:`, `feat:`, `chore:`, or `doc:`.
47 |
48 | - **Imperative Mood**: Write commit messages as commands (e.g., "Add Vagrantfile for VM provisioning").
49 | - **Be Concise but Descriptive**: Provide enough detail to understand the change.
50 | - **Commit Message Conventions**: Use `doc:` for any changes related to documentation.
51 |
52 | ### Submitting Pull Requests
53 |
54 | When submitting pull requests (PRs):
55 |
56 | 1. **Create a Branch**: Create a new branch for your feature or bug fix. Avoid working directly on the `main` branch.
57 | ```bash
58 | git checkout -b feature/your-feature-name
59 | ```
60 |
61 | 2. **Make Atomic Commits**: Ensure each commit is focused and addresses a single change, following the Commit Messages section.
62 |
63 | 3. **Follow Best Practices**: Ensure your code adheres to the project's best practices.
64 |
65 | 4. **Test Your Changes**: Run tests and ensure your changes do not break existing code.
66 |
67 | 5. **Update Documentation**: If your change requires documentation updates, include them in your PR. Use the `documentation` label and provide additional context in the PR description.
68 |
69 | 6. **Submit the PR**: Push your branch to GitHub and open a pull request against the `main` branch. Link the PR to the corresponding issue(s).
70 |
71 | ### Code Style and Best Practices
72 |
73 | - **Use Meaningful Names**: Choose descriptive names for variables and tasks.
74 | - **Keep Tasks Small and Focused**: Each function should perform a single task.
75 | - **DRY (Don’t Repeat Yourself)**: Reuse code wherever possible.
76 | - **Comment Your Code**: Add comments where the code is not self-explanatory.
77 | - **Adhere to Best Practices**: Refer to the [Guidelines for Specific Tasks](#guidelines-for-specific-tasks) for Terraform, Ansible, and Shell Scripting.
78 |
79 | ## Guidelines for Specific Tasks
80 |
81 | ### Terraform
82 |
83 | - **State Management**: Configure remote state management properly, especially when working with remote backends.
84 | - **Output Values**: Make output values informative and useful.
85 | - **Avoid Unnecessary Loops**: Minimize the use of `foreach` and loops for variables, and avoid hardcoding values.
86 |
87 | ### Ansible
88 |
89 | - **Role Organization**: Keep roles modular and reusable.
90 | - **Handlers and Utilities**: Use [handlers](./ansible/handlers/) and [utilities](./ansible/utils/) for frequently repeated tasks.
91 |
92 | ### Shell Scripting
93 |
94 | - **Logging**: Implement consistent logging across all shell scripts. Use a custom logger function as outlined in the TODOs.
95 | - **Error Handling**: Ensure scripts handle errors gracefully and provide informative messages.
96 |
97 | ### CI/CD
98 |
99 | - **GitHub Actions**: Contribute to the existing CI/CD pipeline by implementing automated testing, linting, and security scans for pull requests.
100 |
101 | ## Communication
102 |
103 | - **Stay Updated**: Regularly check for project updates and communicate with maintainers about significant contributions.
104 | - **Respectful Collaboration**: Follow the code of conduct to maintain a respectful and inclusive environment.
105 |
106 | ## License
107 |
108 | By contributing to this project, you agree that your contributions will be licensed under the project's [license](./LICENSE).
109 |
110 | ---
111 |
112 | Thank you for your interest in contributing to the HashiCorp Boundary and Vault Stack! We look forward to your contributions.
--------------------------------------------------------------------------------
/Licence:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Hynek Schlawack and the attrs contributors
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HashiCorp Boundary and Vault Stack
2 |
3 | Deploy a Self-Hosted HCP Vault and Boundary stack using end-to-end automation.
4 |
5 | ## What This Project Offers
6 |
7 | This project provides a comprehensive, hands-on experience in Infrastructure as Code (IaC) and Configuration Management. It simulates a real-world infrastructure environment with a focus on end-to-end automation, enabling DevOps engineers to collaboratively deliver a reliable, production-ready stack. Key deliverables include detailed documentation and diagrams.
8 |
9 | > As of [the latest release](https://github.com/devopshobbies/boundary-vault-stack/releases/latest), BVSTACK covers **steps 0-3** of the [DevOpsHobbies Ultimate Roadmap](https://github.com/devopshobbies/devops-roadmap).
10 |
11 | ## 💻 Toolchain
12 | 
13 | 
14 | 
15 | 
16 | 
17 | 
18 | 
19 | 
20 | 
21 | [](https://www.gnu.org/software/bash/)
22 |
23 | ## Pre-requisites
24 | - [Vagrant](https://developer.hashicorp.com/vagrant/downloads)
25 | - [Virtualbox](https://virtualbox.org/wiki/Linux_Downloads)
26 | - Python => 3.10.12
27 | - Pip
28 | - venv
29 |
30 | ## How to Use
31 | 1. **Read the Documentation**: Before getting started, ensure you have thoroughly reviewed the [project documentation](https://devopshobbies.github.io/boundary-vault-stack/), the [automation workflow diagram](https://linktw.in/nWgoiO) and installed the **prerequisites**.
32 |
33 | 2. **Configure Variables**: Create your own `tfvars` file based on the samples provided in the [Boundary](./boundary/terraform/terraform.tfvars.sample) and [Vault](./vault/terraform/terraform.tfvars.sample) directories. Alternatively, you can remove the `.sample` extension from the provided sample files to use the default values.
34 |
35 | 3. **Run the Start Script**: Begin the setup by running the `start.sh` script in your desired environment:
36 | ```bash
37 | # Run in development:
38 | ./start.sh -e development
39 | ```
40 | > you'll be prompted to choose which NIC you want to bridge to by Vagrant.
41 |
42 | 4. **Enter Vault Password**: You will be prompted to enter the Vault password four times to decrypt Ansible Vault-encrypted files (e.g., `inventory.ini`) unless the related [issue](https://github.com/devopshobbies/boundary-vault-stack/issues/24) is resolved.
43 |
44 | >**Note**: The default `ansible-vault-pass` is `BVSTACK`. This is provided for simplicity in the sample; ensure you use a strong password for your Ansible Vault-encrypted files.
45 |
46 | > **Note**
47 | > The stack assumes that your host machine acts as the Ansible/Terraform controller. If you have the resources, it's recommended to spin up a separate VM to serve as the controller by cloning and running the project on that VM. after that you can export STACK_SERVER environment variable and set it to false this enables you to keep your host machine clean and isolated. Otherwise, don't even bother you won't be losing much. [learn more about STACK_SERVER](https://devopshobbies.github.io/boundary-vault-stack/#environment-variables)
48 |
49 | For further assistance on exit/return codes and configurations, refer to the [documentation](https://devopshobbies.github.io/boundary-vault-stack/).
50 |
51 | ## To-Do List
52 |
53 | ### Terraform
54 |
55 | - [ ] Add a **Vagrantfile** to provision a VM using the **Vagrant** provider of your choice, based on the [specifications](./provision/specs.txt).
56 | - [ ] Provision an **EC2** instance using the **AWS** provider based on the [specifications](./provision/specs.txt) and additional required configurations.
57 | - [ ] Provision an **Azure** VM using the **Azure** provider based on the [specifications](./provision/specs.txt) and additional required configurations.
58 | - [ ] Provision a VM on an ESXi server using the **vSphere** provider based on the [specifications](./provision/specs.txt).
59 | - [ ] Add a remote backend option for Boundary and Vault.
60 | - [ ] Implement additional Vault authentication methods.
61 | - [ ] Enhance Terraform output values for both Boundary and Vault.
62 | - [ ] Implement Policy as Code (PaC) to validate Terraform policies.
63 |
64 | ### Packer
65 |
66 | - [ ] Add a Packer custom image template for VMware vSphere using the [specifications](./provision/specs.txt).
67 |
68 | ### Ansible
69 |
70 | - [ ] Install and configure Terraform on the **control node** using the `prepare_env` role.
71 | - [ ] Install and configure Docker on **target (managed) nodes** using the `prepare_env` role.
72 | - [ ] Template `tfvars` files to handle specific variables for both Boundary and Vault Terraform providers.
73 | - [ ] Create a well-organized Ansible template for Vault and Boundary configurations.
74 | - [ ] Update environment variable declarations in Ansible roles to use the `environment` attribute instead of inline definitions in the `shell` module.
75 | - [ ] Add proper configurations to serve the stack as a reverse proxy in the `serve` directory (tool optional).
76 | - [ ] Update `boundary.yml` to use environment variables instead of hardcoding, then manage the export of these variables with Ansible.
77 | - [ ] Convert Docker Compose files to corresponding Ansible modules using the `community.docker.docker_container` collection as an optional deployment method.
78 | - [ ] Implement Ansible Molecule scenarios to test different aspects of your roles.
79 | - [ ] Choose which provider to provision based on a user-defined or environment variable when handling provisions with Ansible.
80 |
81 | ### CI/CD
82 |
83 | - [ ] Implement automated testing using GitHub Actions for pull requests.
84 |
85 | ### Shell Scripting
86 |
87 | - [ ] Write a custom logger function and implement it throughout all shell scripts for better error handling and logging (in the `log` directory).
88 | - [ ] Use `case` statements instead of `if` for argument handling in `init.sh`.
89 | - [ ] Update `start.sh` to prompt for the Ansible Vault password once and use it for all operations.
90 | - [ ] Replace sleep commands in `start.sh` with the appropriate Ansible `wait_for` modules.
91 | - [ ] Remove the Vault root token in the `cleanup` script.
92 |
93 | ## Contribution
94 |
95 | All contributions are welcome! Please read the [Contributing Guidelines](./CONTRIBUTING.md) for more information.
96 |
97 | ## Credit and Maintenance
98 |
99 | **Copyright © 2024 [Shayan Ghani](https://github.com/Shayan-Ghani) - shayan.ghani.tech@gmail.com**
100 |
--------------------------------------------------------------------------------
/ansible/ansible.cfg:
--------------------------------------------------------------------------------
1 | [defaults]
2 | host_key_checking=false
--------------------------------------------------------------------------------
/ansible/boundary.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: deploy and apply boundary tf modules
3 | hosts: hashicorp
4 | roles:
5 | - boundary
6 |
--------------------------------------------------------------------------------
/ansible/cleanup.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: clean up stack secrets and files
4 | hosts: hashicorp
5 | roles:
6 | - cleanup
7 |
--------------------------------------------------------------------------------
/ansible/handlers/container_healthcheck.yml:
--------------------------------------------------------------------------------
1 | - name: wait for the containers to get ready
2 | ansible.builtin.wait_for:
3 | timeout: 8
4 | delegate_to: localhost
5 |
6 | - name: check if contianer is healthy
7 | debug:
8 | msg: "Container {{ item.container.Name }} is healthy"
9 | with_items :
10 | - "{{ INFO.results }}"
11 | loop_control:
12 | label: "{{ item.container.Name | default('Unknown Container', true ) }}"
13 | when:
14 | - item is defined
15 | - item.container.State.Running | bool
16 | - item.container.State.Restarting != true
17 |
18 | - name: handle errors if occurred
19 | include_tasks: "{{handlers}}/fail_task.yml"
20 | vars:
21 | message: "Container {{ item.container.Name }} is NOT healthy"
22 | with_items :
23 | - "{{ INFO.results }}"
24 | loop_control:
25 | label: "ID : {{ item.container.Id | default('Unknown Container ID', true ) }} , NAME: {{ item.container.Name | default('Unknown Container ID', true ) }}"
26 | when :
27 | - item is not defined or item.container.State.Running != true
28 |
29 | - name: handle errors if container not healthy
30 | include_tasks: "{{handlers}}/service_healthcheck.yml"
31 | vars:
32 | NAMES: "{{ services }}"
33 | PATH: "{{ service_path }}"
34 | with_items :
35 | - "{{ INFO.results }}"
36 | when:
37 | - item is defined
38 | - item.exists != true
39 | - item.container.Name != '/vault'
40 |
41 | - name: fetch the logfile for the unhealthy container(s)
42 | # become: true
43 | fetch:
44 | src: "{{ item.container.LogPath }}"
45 | dest: "{{ log_dir }}/docker/{{item.container.Name}}.txt"
46 | flat: true
47 | fail_on_missing: true
48 | with_items :
49 | - "{{ INFO.results }}"
50 | loop_control:
51 | label: "{{ item.container.LogPath | default('Unknown Container', true ) }}"
52 | when: item.container.State.Running != true
53 |
54 |
--------------------------------------------------------------------------------
/ansible/handlers/fail_task.yml:
--------------------------------------------------------------------------------
1 | - name: Fail if a task failed
2 | fail:
3 | msg: "{{ output.stderr | default(output.stdout, true) | default(message, true) }}"
4 | when: output.rc != 0
--------------------------------------------------------------------------------
/ansible/handlers/service_healthcheck.yml:
--------------------------------------------------------------------------------
1 | - name: fetch the logs for the service(s)
2 | shell: docker compose -f {{ PATH }} logs {{ item }} --no-color
3 | with_items :
4 | - "{{ NAMES }}"
5 | loop_control:
6 | label: "{{ item | default('Unknown Service', true ) }}"
7 | register: service_log
8 | ignore_errors: true
9 |
10 | - name: Save logs to a file
11 | copy:
12 | content: "{{ service_log.stdout | default(service_log.stderr, true) }}"
13 | dest: "{{ log_dir }}/docker/{{ item }}.txt"
14 | with_items:
15 | - "{{ NAMES }}"
16 | loop_control:
17 | label: "{{ item | default('Unknown Service', true ) }}"
18 |
19 | - name: handle errors if occurred
20 | include_tasks: "{{handlers}}/fail_task.yml"
21 | vars:
22 | message: "Service {{ item }} is NOT healthy"
23 | with_items :
24 | - "{{ NAMES }}"
25 | loop_control:
26 | label: "{{ item | default('Unknown Service', true ) }}"
--------------------------------------------------------------------------------
/ansible/host_vars/hashicorp:
--------------------------------------------------------------------------------
1 | # general variables
2 | home_dir: "{{ playbook_dir | dirname }}"
3 | stack_dir: "/home/ubuntu/boundary-vault-stack"
4 | compose_dir: "{{ stack_dir }}/deploy"
5 | vault_addr: "192.168.1.15:8200"
6 | boundary_addr: "192.168.1.15:9200"
7 | handlers: "{{ playbook_dir }}/handlers"
8 | log_dir: "{{ playbook_dir | dirname }}/logs"
9 | secret_dir : "{{stack_dir}}/secrets"
10 |
11 | # environment variables
12 | STACK_ENV: "{{ lookup('env', 'STACK_ENV') }}"
13 | STACK_INIT: "{{ lookup('env', 'STACK_INIT') }}"
14 | SSH_INJECTION: "{{lookup('env', 'SSH_INJECTION')}}"
--------------------------------------------------------------------------------
/ansible/host_vars/localhost:
--------------------------------------------------------------------------------
1 | # general variables
2 | home_dir: "{{ playbook_dir | dirname }}"
3 | stack_dir: "/home/ubuntu/boundary-vault-stack"
4 | compose_dir: "{{ stack_dir }}/deploy"
5 | vault_addr: "127.0.0.1:8200"
6 | boundary_addr: "127.0.0.1:9200"
7 | handlers: "{{ playbook_dir }}/handlers"
8 | log_dir: "{{ playbook_dir | dirname }}/logs"
9 | secret_dir : "{{stack_dir}}/secrets"
10 |
11 | # environment variables
12 | STACK_ENV: "{{ lookup('env', 'STACK_ENV') }}"
13 | STACK_INIT: "{{ lookup('env', 'STACK_INIT') }}"
14 | SSH_INJECTION: "{{lookup('env', 'SSH_INJECTION')}}"
--------------------------------------------------------------------------------
/ansible/inventory/inventory.ini:
--------------------------------------------------------------------------------
1 | $ANSIBLE_VAULT;1.1;AES256
2 | 34356165313161356562656435373638353632373230316332326637663761393366346335306137
3 | 6466353634393666626538626366656238343065616562340a393866313064376137366139353236
4 | 33383062323839306236336461646634666533663739316266366337363533653937383632363133
5 | 3131383562353961620a396530613736313934653339656462336463386639303330346338633435
6 | 65313263633735356262313836316134346636383936653131333934363466626132616133343038
7 | 35616430333563626231363365666536323832383234646637636330353633636436366530363731
8 | 38383936663065633038666336303935313935303965656134373336353564343833316530633934
9 | 61383162396138643632616162323032656638646135633736343832376134316465353831383135
10 | 64646332316166323033383261306161386231356663383332656239386630396366346433646334
11 | 38663966306162663538623264376538616563333831653964303462623162366438373833636333
12 | 37666536303333643131613763326162663337643964653862303030326132373038326236316630
13 | 37626631643130633562653734386536626563653161363136376363353332376366383132666264
14 | 3266
15 |
--------------------------------------------------------------------------------
/ansible/playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: prepare the stack environment and deploy vault
3 | hosts: hashicorp
4 | roles:
5 | - prepare_env
6 | - vault
7 |
--------------------------------------------------------------------------------
/ansible/roles/boundary/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | STACK_INIT: true
3 | STACK_ENV: development
4 | SSH_INJECTION: false
--------------------------------------------------------------------------------
/ansible/roles/boundary/tasks/boundary_iac.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Run terraform configuration
3 | ansible.builtin.shell: HOME_DIR={{ home_dir }} VAULT_TOKEN={{ transit_token }} SSH_INJECTION={{SSH_INJECTION}} bash "{{home_dir}}/scripts/init.sh" boundary
4 | delegate_to: localhost
5 | ignore_errors: true
6 | register: terraform_boundary
7 |
8 | - name: Handle errors if occurred
9 | ansible.builtin.include_tasks: "{{handlers}}/fail_task.yml"
10 | vars:
11 | output: "{{ terraform_boundary }}"
12 |
--------------------------------------------------------------------------------
/ansible/roles/boundary/tasks/deploy.yml:
--------------------------------------------------------------------------------
1 | - name: Get a list of Docker containers
2 | community.docker.docker_container_info:
3 | name : "{{ item }}"
4 | loop : "{{ containers }}"
5 | register: containers_info
6 |
7 | - name: Remove boundary containers if exists.
8 | community.docker.docker_container:
9 | name : "{{ item.container.Id }}"
10 | state: absent
11 | force_kill: yes
12 | with_items :
13 | - "{{ containers_info.results }}"
14 | when : item.exists
15 | loop_control:
16 | label: "{{ item.container.Name | default('Unkown Container', true) }}"
17 |
18 | - name: Remove boundary db volume if not in 'production' env
19 | become: true
20 | file:
21 | path: "{{ vol_path }}"
22 | state: absent
23 | when: STACK_ENV != 'production'
24 | register: vol_absent_result
25 |
26 | - name: Deploy boundary using deploy script
27 | become: true
28 | shell: VAULT_ADDR={{ vault_addr }} VAULT_TOKEN={{ transit_token }} STACK_DIR={{ stack_dir }} bash {{ deploy_script }} boundary
29 | ignore_errors: true
30 | register: boundary_deployment
31 |
32 | - name: handle errors if occurred
33 | include_tasks: "{{handlers}}/fail_task.yml"
34 | vars:
35 | output: "{{ boundary_deployment }}"
36 |
37 | - name: run the docker containers healthcheck
38 | include_tasks: "{{handlers}}/container_healthcheck.yml"
39 | vars:
40 | INFO: "{{ containers_info }}"
41 |
--------------------------------------------------------------------------------
/ansible/roles/boundary/tasks/init_credentials.yml:
--------------------------------------------------------------------------------
1 | - name: fetch the logs for the service(s)
2 | shell: docker compose -f {{ service_path }} logs db-init --no-color | grep -A49 "Initial login"
3 | register: init_creds
4 | ignore_errors: true
5 |
6 | - name: Save credentials to a secrets.txt
7 | shell: echo -e "{{ init_creds.stdout | default(init_creds.stderr, true) }}" > "{{ secret_dir }}/boundary-init-creds.txt"
8 |
9 | - name: retrieve init secrets
10 | include_tasks: "{{playbook_dir}}/utils/secrets.yml"
11 | vars:
12 | secret_file: "boundary-init-creds.txt"
13 | timeout : 3
--------------------------------------------------------------------------------
/ansible/roles/boundary/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: deploy boundary
3 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/deploy.yml"
4 | - name: apply boundary terraform
5 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/boundary_iac.yml"
6 | - name: handle boundary db-init credentials
7 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/init_credentials.yml"
8 |
--------------------------------------------------------------------------------
/ansible/roles/boundary/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | deploy_script: "{{ stack_dir }}/scripts/deploy.sh"
3 | containers:
4 | - boundary
5 | - boundary_db
6 |
7 | services:
8 | - db-init
9 |
10 | service_path: "{{stack_dir}}/deploy/boundary.yml"
11 |
12 | vol_path: /srv/boundary
13 |
--------------------------------------------------------------------------------
/ansible/roles/cleanup/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: cleanup vault tokens
3 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/tokens.yml"
4 | - name: Secrets.txt related clean up ops
5 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/secrets_file.yml"
6 |
--------------------------------------------------------------------------------
/ansible/roles/cleanup/tasks/secrets_file.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Remove the secrets.txt file form the target node(s)
3 | ansible.builtin.file:
4 | path: "{{ secret_file }}"
5 | state: absent
6 |
--------------------------------------------------------------------------------
/ansible/roles/cleanup/tasks/tokens.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Remove transit token from boundary var file
3 | ansible.builtin.shell: HOME_DIR={{home_dir}} bash "{{home_dir}}/scripts/cleanup.sh" -d
4 | delegate_to: localhost
5 |
--------------------------------------------------------------------------------
/ansible/roles/cleanup/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | secret_file: "{{stack_dir}}/secrets/secrets.txt"
3 |
--------------------------------------------------------------------------------
/ansible/roles/prepare_env/tasks/docker_images.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Pull necessary docker images
3 | community.docker.docker_image:
4 | name: "{{ item.value.name }}"
5 | source: pull
6 | tag: "{{ item.value.tag }}"
7 | loop: "{{ docker_images | dict2items }}"
8 | async: 600
9 | poll: 0
10 | register: docker_pull_job
11 |
12 | - name: Extract job IDs
13 | ansible.builtin.set_fact:
14 | job_ids: "{{ docker_pull_job.results | map(attribute='ansible_job_id') | list }}"
15 |
--------------------------------------------------------------------------------
/ansible/roles/prepare_env/tasks/files_and_directories.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Copy all files and directories
3 | become: true
4 | ansible.posix.synchronize:
5 | src: "{{ home_dir }}"
6 | dest: "{{ dest_dir }}"
7 | rsync_opts:
8 | # - "--exclude=.git/"
9 | - --exclude=.gitignore
10 | - --exclude=README.md
11 | - --exclude=ansible/
12 | - --exclude=venv/
13 |
14 | - name: Create main directory structure
15 | ansible.builtin.file:
16 | path: "{{ dest_dir }}"
17 | state: directory
18 | mode: "0755"
19 |
--------------------------------------------------------------------------------
/ansible/roles/prepare_env/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: pull required docker images
3 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/docker_images.yml"
4 |
5 | - name: copy and sync files and directories
6 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/files_and_directories.yml"
7 |
--------------------------------------------------------------------------------
/ansible/roles/prepare_env/vars/main.yml:
--------------------------------------------------------------------------------
1 | dest_dir: /home/ubuntu/
2 |
3 | docker_images:
4 | vault:
5 | name: hashicorp/vault
6 | tag: 1.13.3
7 | postgresql:
8 | name: bitnami/postgresql
9 | tag: 16
10 | boundary:
11 | name: hashicorp/boundary
12 | tag: 0.14
13 | busybox:
14 | name: busybox
15 | tag: latest
--------------------------------------------------------------------------------
/ansible/roles/provision/default/main.yml:
--------------------------------------------------------------------------------
1 | # handle default values for provisioners here.
--------------------------------------------------------------------------------
/ansible/roles/provision/tasks/main.yml:
--------------------------------------------------------------------------------
1 | # Implement Different Provisioners on seperated playbooks and include them here
2 |
--------------------------------------------------------------------------------
/ansible/roles/provision/vars/main.yml:
--------------------------------------------------------------------------------
1 | ### handle variable for different provisioners seperate by #[Provisioner_Name].
2 |
3 | ### for instance : # [Vagrant]
4 | ### some_task:
--------------------------------------------------------------------------------
/ansible/roles/terraform/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | SSH_INJECTION: Flase
3 |
--------------------------------------------------------------------------------
/ansible/roles/terraform/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Restart_ssh
3 | become: true
4 | ansible.builtin.service:
5 | name: sshd
6 | state: restarted
7 |
--------------------------------------------------------------------------------
/ansible/roles/terraform/tasks/cred_store_ssh.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Configure ssh public_key from vault
3 | become: true
4 | ansible.builtin.shell: cp {{stack_dir}}/secrets/ca-key.pub /etc/ssh/ca-key.pub && chown 1000:1000 /etc/ssh/ca-key.pub && chmod 644 /etc/ssh/ca-key.pub && echo "TrustedUserCAKeys
5 | /etc/ssh/ca-key.pub" >> /etc/ssh/sshd_config
6 | notify: restart_ssh
7 | when: SSH_INJECTION == True
8 |
9 | - name: Add ssh cred store token to variables
10 | ansible.builtin.shell: bash "{{home_dir}}/scripts/cleanup.sh" ssh
11 | delegate_to: localhost
12 | when: SSH_INJECTION == True
13 |
--------------------------------------------------------------------------------
/ansible/roles/terraform/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: credential store token ssh preparation
3 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/cred_store_ssh.yml"
4 | - name: handle vault transit in tfvars
5 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/tfvars_token.yml"
6 | - name: apply terraform for vault
7 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/vault_iac.yml"
8 |
--------------------------------------------------------------------------------
/ansible/roles/terraform/tasks/tfvars_token.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add transit token to boundary tfvars file
3 | ansible.builtin.shell: HOME_DIR={{home_dir}} bash "{{home_dir}}/scripts/cleanup.sh"
4 | delegate_to: localhost
5 | ignore_errors: true
6 | register: tfvars_token_injection
7 |
8 | - name: Handle errors if occurred
9 | ansible.builtin.include_tasks: "{{handlers}}/fail_task.yml"
10 | vars:
11 | output: "{{ tfvars_token_injection }}"
12 |
--------------------------------------------------------------------------------
/ansible/roles/terraform/tasks/vault_iac.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Run vault terraform configuration
3 | ansible.builtin.shell: HOME_DIR={{home_dir}} VAULT_ADDR={{VAULT_ADDRESS}} bash "{{home_dir}}/scripts/init.sh" vault
4 | delegate_to: localhost
5 | ignore_errors: true
6 | register: vault_terraform
7 |
8 | - name: Handle errors if occurred
9 | ansible.builtin.include_tasks: "{{handlers}}/fail_task.yml"
10 | vars:
11 | output: "{{ vault_terraform }}"
12 |
--------------------------------------------------------------------------------
/ansible/roles/terraform/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | VAULT_ADDRESS: http://192.168.1.15:8200
3 |
--------------------------------------------------------------------------------
/ansible/roles/vault/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | STACK_INIT: true
3 | STACK_ENV: development
4 | secret_file: secrets.txt
5 |
--------------------------------------------------------------------------------
/ansible/roles/vault/tasks/deploy.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create hcp external
3 | become: true
4 | community.docker.docker_network:
5 | name: hashicorp
6 |
7 | - name: Remove vault volume if not in 'production' env
8 | become: true
9 | ansible.builtin.file:
10 | path: "{{ vol_path }}"
11 | state: absent
12 | when: STACK_ENV != 'production'
13 | register: vol_absent_result
14 |
15 | - name: Get a list of Docker containers
16 | community.docker.docker_container_info:
17 | name: "{{ item }}"
18 | loop: "{{ containers }}"
19 | register: containers_info
20 |
21 | - name: Remove vault containers if exists
22 | community.docker.docker_container:
23 | name: "{{ item.container.Id }}"
24 | state: absent
25 | force_kill: true
26 | with_items:
27 | - "{{ containers_info.results }}"
28 | loop_control:
29 | label: "ID : {{ item.container.Id | default('Unkown Container', true) }} , NAME: {{ item.container.Name | default('Unkown Container', true) }}"
30 | when: item.exists
31 |
32 | - name: Deploy vault using deploy_script
33 | become: true
34 | ansible.builtin.shell: STACK_DIR={{ stack_dir }} STACK_INIT={{ STACK_INIT }} bash {{ deploy_script }} vault
35 | ignore_errors: true
36 | register: vault_deployment
37 |
38 | - name: Handle errors if occurred
39 | ansible.builtin.include_tasks: "{{handlers}}/fail_task.yml"
40 | vars:
41 | output: "{{ vault_deployment }}"
42 |
43 | - name: Get a list of Docker containers
44 | community.docker.docker_container_info:
45 | name: "{{ item }}"
46 | loop: "{{ containers }}"
47 | register: containers_info
48 |
49 | - name: run the docker containers healthcheck
50 | ansible.builtin.include_tasks: "{{handlers}}/container_healthcheck.yml"
51 | vars:
52 | INFO: "{{ containers_info }}"
53 |
--------------------------------------------------------------------------------
/ansible/roles/vault/tasks/docker_pull_status.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check docker pull status
3 | ansible.builtin.async_status:
4 | jid: "{{ item }}"
5 | register: pull_result
6 | until: pull_result.finished
7 | retries: 10
8 | delay: 60
9 | loop: "{{job_ids}}"
10 |
11 | - name: Clean up async file
12 | ansible.builtin.async_status:
13 | jid: "{{ item }}"
14 | mode: cleanup
15 | loop: "{{job_ids}}"
16 |
--------------------------------------------------------------------------------
/ansible/roles/vault/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check the status of docker images jobs
3 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/docker_pull_status.yml"
4 |
5 | - name: deploy vault
6 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/deploy.yml"
7 |
8 | - name: retrieve vault secrets
9 | ansible.builtin.include_tasks: "{{ role_path }}/tasks/secrets.yml"
10 |
--------------------------------------------------------------------------------
/ansible/roles/vault/tasks/secrets.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Make sure the old secret file is absent
3 | become: true
4 | ansible.builtin.file:
5 | path: "{{ home_dir }}/secrets/{{ secret_file }}"
6 | state: absent
7 | delegate_to: localhost
8 |
9 | - name: Wait for secret file to get generated
10 | ansible.builtin.wait_for:
11 | timeout: 10
12 |
13 | - name: Fetch the secret file to control node
14 | become: true
15 | ansible.builtin.fetch:
16 | src: "{{ secret_dir }}/{{ secret_file }}"
17 | dest: "{{ home_dir }}/secrets/{{ secret_file }}"
18 | flat: true
19 | fail_on_missing: true
20 |
21 | - name: Print at the secret file path
22 | ansible.builtin.debug:
23 | msg: You can now see Vault secrets at `{{ home_dir }}/secrets/{{ secret_file }}`
24 |
--------------------------------------------------------------------------------
/ansible/roles/vault/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | deploy_script: "{{ stack_dir }}/scripts/deploy.sh"
3 | containers:
4 | - vault
5 |
6 | vol_path: /srv/vault
7 |
--------------------------------------------------------------------------------
/ansible/terraform.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name : ensure terraform requirments and apply tf modules
3 | hosts: hashicorp
4 | roles:
5 | - terraform
--------------------------------------------------------------------------------
/ansible/utils/secrets.yml:
--------------------------------------------------------------------------------
1 | - name: make sure the old secret file is absent
2 | become: true
3 | ansible.builtin.file:
4 | path: "{{home_dir}}/secrets/{{ secret_file }}"
5 | state: absent
6 | delegate_to : localhost
7 |
8 | - name: wait for secret file to get generated
9 | wait_for:
10 | timeout: "{{timeout}}"
11 |
12 | - name: fetch the secret file to control node
13 | become: true
14 | fetch:
15 | src: "{{ secret_dir }}/{{ secret_file }}"
16 | dest: "{{home_dir}}/secrets/{{ secret_file }}"
17 | flat: yes
18 | fail_on_missing: true
19 |
20 | - name : Print at the secret file path
21 | debug:
22 | msg: "You can now see the secrets at `{{home_dir}}/secrets/{{ secret_file }}`"
--------------------------------------------------------------------------------
/artifacts/diagrams/boundary.py:
--------------------------------------------------------------------------------
1 | from diagrams import Diagram, Cluster, Edge, Node
2 | from diagrams.onprem.compute import Server
3 | from diagrams.onprem.auth import Boundary
4 | from diagrams.onprem.security import Vault
5 | from diagrams.onprem.database import PostgreSQL
6 | from diagrams.oci.governance import Audit, Logging
7 |
8 |
9 | graph_attr = {
10 | "fontname": "Roboto",
11 | "fontsize": "24"
12 | }
13 |
14 | with Diagram("Boundary server Workflow", show=False, direction="LR", graph_attr=graph_attr, filename="boundary"):
15 | boundary_controller = Boundary("Boundary Controller")
16 | boundary_worker = Boundary("Boundary Worker")
17 | vault = Vault("Vault Transit Engine")
18 |
19 | with Cluster("Listeners"):
20 | api_listener = Server("API Listener")
21 | cluster_listener = Server("Cluster Listener")
22 | proxy_listener = Server("Proxy Listener")
23 | Node(label="", width="2", height="0", style="invisible")
24 |
25 |
26 | with Cluster("Audit Event Sinks"):
27 | audit_file_sink = Audit("Controller")
28 | auth_sink = Audit("Auth Observation")
29 | session_sink = Audit("Session Authorization")
30 | Node(label="", width="2", height="0", style="invisible")
31 | stderr_sink = Logging("Stderr Sink")
32 |
33 | with Cluster("KMS Keys"):
34 | recovery_key = Boundary("recovery")
35 | worker_auth = Boundary("worker-ath")
36 | root_key = Boundary("root")
37 |
38 | postgres= PostgreSQL("Postgresql")
39 |
40 | # Controller connections
41 | boundary_controller >> Edge(label="TCP connection") >> cluster_listener
42 | boundary_controller >> Edge(label="Audit File Events") >> auth_sink
43 | boundary_controller >> Edge(label="All-events") >> stderr_sink
44 |
45 | # Worker connections
46 | boundary_worker >> Edge(label="Connected to Controller") >> boundary_controller
47 |
48 | # KMS connections
49 | worker_auth >> Edge() >> vault
50 | recovery_key >> Edge() >> vault
51 | root_key >> Edge() >> vault
52 | root_key << Edge(attrs="penwidth: 2.0") << boundary_controller
53 |
54 | # DB connections
55 | postgres << Edge(label="DB Connection") << boundary_controller
--------------------------------------------------------------------------------
/artifacts/diagrams/vault.py:
--------------------------------------------------------------------------------
1 | from diagrams import Diagram, Cluster, Edge, Node
2 | from diagrams.onprem.security import Vault
3 | from diagrams.generic.storage import Storage
4 | from diagrams.onprem.client import Users
5 |
6 | with Diagram("\nVault Server Workflow", show=False, direction="RL", graph_attr={"fontname" : "arial", "fontsize": "28" }, filename="vault"):
7 | cluster_attr= {
8 | "margin" : "20",
9 | "fontsize": "16",
10 | "fontname" : "arial"}
11 |
12 | with Cluster("Vault Setup", graph_attr=cluster_attr):
13 | vault_listener = Vault("TCP Listener")
14 | storage_raft = Storage("\nRaft Storage")
15 | vault_ui = Vault("UI")
16 |
17 | with Cluster("User Management", graph_attr=cluster_attr):
18 | userpass_lockout = Users("\nUserpass Lockout")
19 | users = Users("\nUsers")
20 |
21 | # Vault connections
22 | vault_listener - Edge(label="0.0.0.0:8200\nTLS Disabled") >> vault_ui
23 | vault_listener >> Edge(label="Max Entry Size\n1MB") >> storage_raft
24 |
25 | # User Management connections
26 | users - Edge(label="Lockout Threshold: 3\nLockout Duration: 10m") - userpass_lockout
27 |
28 | # External connections
29 | api_addr = Vault("API Address\nhttp://localhost:8200")
30 | cluster_addr = Vault("Cluster Address\nhttp://127.0.0.1:8201")
31 |
32 | vault_listener >> Edge(label="API and Cluster Addresses") >> [api_addr, cluster_addr]
33 |
--------------------------------------------------------------------------------
/artifacts/wiki.md:
--------------------------------------------------------------------------------
1 | # Boundary-Vault-Stack
2 |
3 | ## Table of Contents
4 | - [Getting Started](#getting-started)
5 | - [About Hashicorp Vault and Boundary](#about-hashicorp-vault-and-boundary)
6 | - [Workflows](#workflows)
7 | - [Vault](#vault)
8 | - [Boundary](#boundary)
9 | - [Configurations](#configurations)
10 | - [Environment Variables](#environment-variables)
11 | - [Return/Exit Codes](#returnexit-codes)
12 | - [Bear In Mind](#bear-in-mind)
13 |
14 |
15 | ## Getting started
16 | After the server is properly provision and configured you'll have Vault and Boundary up and running. For the sake of education the stack will be initialized with minimum resources for both services including KV and Transit engine (vault) and a series of auth-method, host catalog, credential stores, etc (Boundary). As the Contributions increase, the resources will be enriched accordingly covering more arbitrary resources and features in the format of IaC.
17 |
18 | To grasp what's going on under the hood, you can reach out to the section you wish to explore in this documentation.
19 |
20 |
21 | ## About Hashicorp Vault and Boundary
22 |
23 | According to Hashicorp documentation,
24 |
25 | [Boundary](https://developer.hashicorp.com/boundary/docs/overview/what-is-boundary) is an identity-aware proxy that simplifies and secures least-privileged access to cloud infrastructure. It enables SSO, just-in-time access, dynamic credentials, and session management.
26 |
27 | [Vault](https://developer.hashicorp.com/vault/docs/what-is-vault) is an identity-based secrets and encryption management system. A secret is anything that you want to tightly control access to, such as API encryption keys, passwords, and certificates. Vault provides encryption services that are gated by authentication and authorization methods. Using Vault’s UI, CLI, or HTTP API, access to secrets and other sensitive data can be securely stored and managed, tightly controlled (restricted), and auditable.
28 |
29 | ### learn more:
30 | - [Boundary](https://youtu.be/tUMe7EsXYBQ?si=3IFGbMLGTEy_3X1T)
31 | - [Vault](https://youtu.be/VYfl-DpZ5wM?si=a3Ign_zoUNS92EAP)
32 |
33 | ## Workflows
34 | ### Vault
35 |
36 |
37 |
38 | ### Boundary
39 |
40 |
41 |
42 | ## Configurations
43 |
44 | ### Environment Variables
45 |
46 | #### STACK_ENV (mandatory)
47 | **Description**: This variable determies in which mode/environment the stack is deployed.
48 |
49 | **Options**:
50 | - development
51 | - test
52 | - staging
53 | - production
54 |
55 | **default**: development
56 |
57 | ---
58 |
59 | #### STACK_INIT (mandatory)
60 |
61 | **Description**: When **first** running the stack, `vault-init` and `boundary-init` services are in charge of initiating the basic configurations for `boundary` and `vault`. This variable determines wether this services should be executed or not. So if it's **not** your first time running the stack successfully, set to `false`.
62 |
63 | **Options**:
64 | - true
65 | - false
66 |
67 | **default**: true
68 |
69 | ---
70 |
71 | #### SSH_INJECTION (optional)
72 |
73 | **Description**:
74 |
75 | **Options**:
76 | - true
77 | - false
78 |
79 | ## Return/Exit Codes
80 |
81 | In this project, several scripts use return/exit codes to indicate the result of operations. Understanding these codes is essential for diagnosing issues and ensuring proper execution of the scripts. Below is a detailed explanation of each return/exit code used in the project.
82 |
83 | ### Exit Code 1: Service Not Installed
84 |
85 | **Description**: This exit code indicates that the required service is not installed on the system.
86 |
87 | **Possible Causes**:
88 | - The service was not installed during the setup process.
89 | - The installation process was interrupted or failed.
90 |
91 | **Resolution**:
92 | - Verify and Ensure that the installation was successful by running the `prepare_env` role.
93 |
94 | **Example**:
95 | ```bash
96 | scripts/init.sh vault
97 | # Output: Terraform not installed
98 | # Exit code: 1
99 | ```
100 |
101 | ---
102 |
103 | ### Exit Code 2: Terraform Init Failed
104 |
105 | **Description**: This exit code indicates that the `terraform init` command failed.
106 |
107 | **Possible Causes**:
108 | - The Terraform configuration files are missing or corrupted.
109 | - There is a network issue preventing Terraform from accessing necessary modules or providers.
110 | - Incorrect permissions to the directory where Terraform is being initialized.
111 | - Wrong terraform directory path
112 |
113 | **Resolution**:
114 | - Ensure that all required Terraform configuration files are present and correctly configured.
115 | - Check network connectivity and permissions.
116 | - Refer to the [Terraform Documentation](https://www.terraform.io/docs/commands/init.html) for more details.
117 |
118 | **Example**:
119 | ```bash
120 | scripts/init.sh vault
121 | # Output: Terraform init failed
122 | # Exit code: 2
123 | ```
124 |
125 | ---
126 |
127 | ### Exit Code 3: Configuration is Invalid
128 |
129 | **Description**: This exit code indicates that `terrafrom validate` was not successfuly executed.
130 |
131 | **Possible Causes**:
132 | - The configuration file has syntax errors.
133 | - Required configuration parameters are missing or incorrect.
134 |
135 | **Resolution**:
136 | - Validate the configuration file against the expected schema.
137 | - Ensure all required parameters are correctly specified.
138 |
139 | **Example**:
140 | ```bash
141 | scripts/init.sh vault
142 | # Output: Configuration is invalid
143 | # Exit code: 3
144 | ```
145 | ---
146 |
147 | ### Exit Code 4: Arguments and Options are Invalid
148 |
149 | **Description**: This exit code indicates that the arguments or options passed to the script are invalid.
150 |
151 | **Possible Causes**:
152 | - Incorrect or missing arguments/options.
153 | - The script was invoked with unsupported options.
154 |
155 | **Resolution**:
156 | - Refer to the script usage documentation to ensure all required arguments and options are correctly specified.
157 | - Use the `--help` option with the script to view the correct usage.
158 |
159 | **Example**:
160 | ```bash
161 | ./start.sh --environment development
162 | # Output: Invalid option: --environment
163 | # Exit code: 4
164 | ```
165 |
166 | ## Bear In Mind
167 | - If you have issues with DockerHub make sure you change the image registry in deployments and `prepare_env` role.
168 |
169 | - If the target node(s) get restarted, the `vault` gets sealed and `boundary` container will be in restarting mode.
170 |
171 | - In case the `vault` container gets restarted, it will be sealed and you'll have an error on your `boudary` container, There manage to get them working together again.
172 |
173 | - You can additionally add session recording and other paid plan features.
174 |
175 | - Vault is initialized with 1 shared-key to simplify the process, consider increasing the number of keys and threshold for better security.
176 |
177 | ## Still Having Issues?
178 |
179 | For further assistance, feel free to open up a new issue on the [GitHub Issues page](https://github.com/devopshobbies/boundary-vault-stack/issues).
180 |
--------------------------------------------------------------------------------
/artifacts/wiki/app.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('scroll', function () {
2 | const headers = document.querySelectorAll('.section-header');
3 | const pageOffset = window.scrollY;
4 |
5 | headers.forEach(header => {
6 | const headerOffset = header.offsetTop;
7 | const headerHeight = header.offsetHeight;
8 |
9 | if (pageOffset >= headerOffset - headerHeight && pageOffset < headerOffset + headerHeight) {
10 | header.classList.add('active');
11 | } else {
12 | header.classList.remove('active');
13 | }
14 | });
15 | });
16 |
17 | function copyToClipboard() {
18 | const codeElement = document.querySelector('.code');
19 | const range = document.createRange();
20 | range.selectNode(codeElement);
21 | window.getSelection().removeAllRanges();
22 | window.getSelection().addRange(range);
23 | document.execCommand('copy');
24 | window.getSelection().removeAllRanges();
25 |
26 |
27 | const notification = document.getElementById('copy-notification');
28 | notification.classList.add('show');
29 |
30 | setTimeout(() => {
31 | notification.classList.remove('show');
32 | }, 3000);
33 | }
--------------------------------------------------------------------------------
/artifacts/wiki/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Boundary-Vault-Stack Documentation
8 |
9 |
10 |
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Boundary-Vault-Stack Documentation
25 |
26 |
27 |
28 |
29 |
47 |
48 |
49 |
50 |
51 |
52 |
After the server is properly provisioned and configured, you'll have Vault and Boundary up and
53 | running.
54 | For the sake of education, the stack will be initialized with minimum resources for both services,
55 | including KV and Transit engine Vault and a series of auth-methods, host
56 | catalog, credential
57 | stores, etc. Boundary . As the contributions increase, the resources will
58 | be enriched accordingly, covering
59 | more arbitrary resources and features in the format of Infrastructure as Code (IaC).
60 |
To grasp what's going on under the hood, you can reach out to the section you wish to explore in this
61 | documentation.
62 |
63 |
About Hashicorp Vault and Boundary
64 |
65 |
According to Hashicorp documentation:
66 |
Boundary is an
67 | identity-aware proxy that simplifies and secures least-privileged access to cloud infrastructure. It
68 | enables SSO, just-in-time access, dynamic credentials, and session management.
69 |
Vault is an identity-based
70 | secrets
71 | and encryption management system. A secret is anything that you want to tightly control access to,
72 | such
73 | as API encryption keys, passwords, and certificates. Vault provides encryption services that are
74 | gated
75 | by authentication and authorization methods. Using Vault’s UI, CLI, or HTTP API, access to secrets
76 | and
77 | other sensitive data can be securely stored, managed, tightly controlled (restricted), and audited.
78 |
79 |
Learn more:
80 |
84 |
85 |
86 |
87 |
88 |
After the environment is prepared, Ansible deploys Vault using Docker Compose. The Vault server is
89 | first initialized by the vault-init service, which runs the `init.sh`
90 | script and calls the `init_vault_setup` function. This process unseals Vault, creates the necessary
91 | secret engines, policies, tokens, and other basic configurations. Once the deployment is complete, the
92 | Transit-token and secrets.txt file, containing
93 | all the essential tokens and keys, are generated and stored in the `secrets/secrets.txt` file on the
94 | controller machine.
95 |
96 | Next, the Terraform role is applied to configure Vault, utilizing the generated tokens and
97 | variables. Following this, the deployment shifts focus to Boundary. To enable Boundary to use
98 | Vault's Transit Engine for Encryption as a Service (EaaS), the Transit token (already stored in
99 | `secrets.txt`) is passed to the Boundary role. Once the Boundary database (PostgreSQL) is up and
100 | running, it is initialized using the db-init service. Finally, Boundary is
101 | deployed with the initial credentials saved in secrets/boundary-init-creds.txt . Terraform resources are then applied using
103 | the Vault Transit recovery key and Transit
104 | token .
105 |
106 | To use the stack, you need to install the Boundary and Vault clients (ensure the server and client
107 | versions match). Once installed, you can start using BVSTACK.
108 |
109 | You can checkout the boundary UI at http://192.168.1.15:9200 and Vault at http://192.168.1.15:8200 .
110 |
111 |
112 |
113 |
114 |
Vault workflow involves setting up authentication methods, secret engines, and policies. The key
115 | components of Vault server setup include:
116 |
117 |
118 |
119 |
Boundary workflow involves managing sessions, targets, and credentials. The key
120 | components of Boundary server setup include:
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
Environment variables need to be configured for both Vault and Boundary. Here's an example:
131 |
STACK_ENV (mandatory)
132 |
This variable determines in which mode/environment the stack is deployed.
133 |
134 | development
135 | test
136 | staging
137 | production
138 |
139 |
default : development
140 |
141 |
STACK_INIT (mandatory)
142 |
When first running the stack, vault-init and
143 | boundary-init services are in charge of initiating the basic
144 | configurations for Boundary and
145 | `vault`. This variable determines whether these services should be executed or not. So if it's not your first time running the stack successfully, set to false .
148 |
149 |
150 | true
151 | false
152 |
153 |
default : true
154 |
155 |
SSH_INJECTION (optional)
156 |
SSH injection variable enables Boundary vault credential store. only works on paid
157 | plan!!
158 |
159 | true
160 | false
161 |
162 |
default : false
163 |
164 |
STACK_SERVER (optional)
165 |
If set to false, vagrant and virtualbox won't be used to spin up BVSTACK. Instead you must create
166 | both Controller, BVSTACK and Client machines manually using your prefered
167 | method; ensure to address them in the inventory file accordingly.
168 |
169 | true
170 | false
171 |
172 |
default : true
173 |
174 |
175 |
176 |
177 |
178 |
In this project, several scripts use return/exit codes to indicate the result of operations.
179 | Understanding these codes is essential for diagnosing issues and ensuring proper execution of the
180 | scripts. Below is a detailed explanation of each return/exit code used in the project.
181 |
182 |
Exit Code 1: Service Not Installed
183 |
Description: This exit code indicates that the required service is not
184 | installed on the system.
185 |
Possible Causes:
186 |
187 | The service was not installed during the setup process.
188 | The installation process was interrupted or failed.
189 |
190 |
Resolution:
191 |
192 | Verify and ensure that the installation was successful by running the prepare_env role.
194 |
195 |
196 |
Resolution:
197 |
198 |
199 |
200 |
201 |
$ scripts/init.sh vault
202 |
203 | # Output: Terraform not installed
204 | # Exit code: 1
205 |
206 |
207 |
208 |
Copied!
209 |
210 |
Exit Code 2: Terraform Init Failed
211 |
Description: This exit code indicates that the `terraform init` command
212 | failed.
213 |
Possible Causes:
214 |
215 | The Terraform configuration files are missing or
216 |
217 | corrupted.
218 | There is a network issue preventing Terraform from accessing necessary modules or providers.
219 |
220 | Incorrect permissions to the directory where Terraform is being initialized.
221 | Wrong terraform directory path.
222 |
223 |
Resolution:
224 |
225 | Ensure that all required Terraform configuration files are present and correctly configured.
226 |
227 | Refer to the Terraform Documentation for more details.
229 | Check network connectivity and permissions.
230 |
231 |
232 |
Resolution:
233 |
234 |
235 |
236 |
237 |
238 | $ scripts/init.sh vault
239 |
240 | # Output: Terraform init failed
241 | # Exit code: 2
242 |
243 |
244 |
245 |
Exit Code 3: Configuration is Invalid
246 |
Description: This exit code indicates that `terraform validate` was not
247 | successfully executed.
248 |
Possible Causes:
249 |
250 | The configuration file has syntax errors.
251 | Required configuration parameters are missing or incorrect.
252 |
253 |
Resolution:
254 |
255 | Validate the configuration file against the expected schema.
256 | Ensure all required parameters are correctly specified.
257 |
258 |
259 |
Resolution:
260 |
261 |
262 |
263 |
264 |
265 | $ scripts/init.sh vault
266 |
267 | # Output: Configuration is invalid
268 | # Exit code: 3
269 |
270 |
271 |
272 |
Exit Code 4: Arguments and Options are Invalid.
273 |
Description: This exit code indicates that the arguments or options passed
274 | to the script are invalid.
275 |
Possible Causes:
276 |
277 | Incorrect or missing arguments/options.
278 | The script was invoked with unsupported options.
279 |
280 |
Resolution:
281 |
282 | Refer to the script usage documentation to ensure all required arguments and options are
283 | correctly specified.
284 | Use the `--help` option with the script to view the correct usage.
285 |
286 |
287 |
Resolution:
288 |
289 |
290 |
291 |
292 |
293 | $ ./start.sh --environment development
294 |
295 | # Output: Invalid option: --environment
296 | # Exit code: 4
297 |
298 |
299 |
300 |
301 |
302 |
303 |
Bear In Mind
304 |
Keep the following in mind when working with the Boundary-Vault stack:
305 |
306 | Since Boundary Terraform uses Vault recovery transit key you must export the transit-token as
307 | VAULT_TOKEN
308 | (secrets/secrets.txt) before planning/applying the code. Otherwise,
309 | you should use auth-method credentials to communicate with the boundary api.
310 |
311 | If you have issues with DockerHub make sure you change the image registry in deployments and
312 | `prepare_env` role.
313 | If the target node(s) get restarted, the vault gets sealed and boundary container will be in
315 | restarting mode.
316 | In case the vault container gets restarted, it will be sealed and
317 | you'll have an error on your
318 | boundary container, there manage to get them working together again.
319 |
320 | You can additionally add session recording and other paid plan features.
321 | Vault is initialized with 1 shared-key to simplify the process, consider increasing the number
322 | of keys and threshold for better security.
323 |
324 |
325 |
326 |
327 |
328 |
For further assistance, feel free to open up a new issue on the GitHub Issues page.
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
--------------------------------------------------------------------------------
/artifacts/wiki/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --bold-default: 800;
3 | --bold-text: 500;
4 | --bg-primary: #f9f9f9;
5 | --clr-text: #262626;
6 | --clr-primary: #2b3e50;
7 | --clr-secondary: #007acc;
8 | --clr-header: #fff;
9 | --code-primary-clr: #33333344;
10 | --clr-code-light: #ffffffba;
11 | --font-primary: 'Roboto', sans-serif;
12 | --font-code: 'Fira Code', monospace;
13 | --font-size-large: 1.6rem;
14 | --font-size-mid: 1.4rem;
15 | --font-size-small: 1rem;
16 | --font-size-phone: 1.2rem;
17 | --margin-small: 1.6rem;
18 | --margin-mid: 3rem;
19 | --margin-large: 5rem;
20 | }
21 |
22 | html {
23 | scroll-behavior: smooth;
24 | }
25 |
26 | body {
27 | font-family: var(--font-primary);
28 | margin: 0;
29 | padding: 0;
30 | background-color: var(--bg-primary);
31 | color: var(--clr-text);
32 | line-height: 2.5rem;
33 | }
34 |
35 | header {
36 | background-color: var(--clr-primary);
37 | color: var(--clr-header);
38 | padding: 1rem;
39 | text-align: center;
40 | font-size: var(--font-size-mid);
41 | font-weight: 700;
42 | }
43 |
44 | .container {
45 | display: flex;
46 | justify-content: center;
47 | align-items: center;
48 | padding: 20px;
49 | font-size: var(--font-size-mid);
50 | }
51 |
52 | nav {
53 | flex: 1;
54 | align-self: self-start;
55 | position: sticky;
56 | top: 20px;
57 | max-width: 200px;
58 | font-size: var(--font-size-small);
59 | }
60 |
61 | nav ul {
62 | list-style-type: none;
63 | padding: 0;
64 | }
65 |
66 | nav ul li a {
67 | text-decoration: none;
68 | color: var(--clr-primary);
69 | padding: 8px 0;
70 | display: block;
71 | font-weight: var(--bold-text);
72 | }
73 |
74 | nav ul li a:hover {
75 | color: var(--clr-secondary);
76 | }
77 |
78 | .content {
79 | flex: 3;
80 | padding: 0 20px;
81 | }
82 |
83 | h2 {
84 | color: var(--clr-primary);
85 | font-size: 1.75rem;
86 | margin-top: 1rem;
87 | }
88 |
89 | h3 {
90 | color: var(--clr-primary);
91 | font-size: 1.5rem;
92 | margin-top: 1rem;
93 | }
94 |
95 |
96 | a {
97 | color: var(--clr-secondary);
98 | text-decoration: none;
99 | }
100 |
101 | a:hover {
102 | text-decoration: underline;
103 | }
104 |
105 | /* text style */
106 | .bold {
107 | font-weight: var(--bold-text);
108 | }
109 |
110 | .i {
111 | background-color: var(--code-primary-clr);
112 | padding: 2px 7px;
113 | /* border-radius: 5px; */
114 | }
115 |
116 | .default {
117 | font-weight: var(--bold-default);
118 | }
119 |
120 |
121 |
122 | /* code style */
123 |
124 | .code {
125 | margin: 0;
126 | }
127 |
128 | .code-container {
129 | position: relative;
130 | background-color: var(--clr-primary);
131 | color: var(--clr-header);
132 | padding: 15px 20px;
133 | border-radius: 10px;
134 | font-family: var(--font-code);
135 | font-size: var(--font-size-small);
136 | /* overflow-x: auto; */
137 | cursor: pointer;
138 | max-width: 768px;
139 | height: 160px;
140 | }
141 |
142 | .copy-btn {
143 | background-color: transparent;
144 | position: absolute;
145 | top: 10px;
146 | right: 10px;
147 | border: none;
148 | border-radius: 5px;
149 | padding: 5px 10px;
150 | cursor: pointer;
151 | font-size: var(--font-size-mid);
152 | transition: color 0.3s;
153 | color: var(--clr-header);
154 | }
155 |
156 | .copy-btn:hover {
157 | /* Change to your desired hover color */
158 | color: var(--clr-secondary);
159 | /* Change to your desired text color on hover */
160 | }
161 |
162 | #copy-notification {
163 | position: fixed;
164 | bottom: -50px;
165 | /* Start off-screen */
166 | left: 50%;
167 | transform: translateX(-50%);
168 | background-color: var(--clr-secondary);
169 | color: var(--bg-primary);
170 | padding: 10px 20px;
171 | border-radius: 5px;
172 | font-size: 14px;
173 | opacity: 0;
174 | transition: bottom 0.5s ease, opacity 0.5s ease;
175 | z-index: 1000;
176 | }
177 |
178 | #copy-notification.show {
179 | bottom: 30px;
180 | opacity: 1;
181 | }
182 |
183 | .code span {
184 | color: var(--clr-code-light);
185 | }
186 |
187 | /* end of code styling */
188 |
189 |
190 | /* scroll animation */
191 | .section-header {
192 | padding-top: 20px;
193 | transition: color 0.3s ease, background-color 0.3s ease;
194 | color: var(--clr-primary);
195 | }
196 |
197 | .section-header.active {
198 | color: var(--clr-secondary);
199 | }
200 |
201 | /* end of scroll animation */
202 |
203 | .rc .code-container {
204 | margin-bottom: var(--margin-small);
205 | }
206 |
207 | /* workflows */
208 | .workflows {
209 | display: flex;
210 | flex-direction: column;
211 | max-width: 100%;
212 | margin: 0 auto;
213 | padding: 20px;
214 | }
215 | .workflows img {
216 | width: 100%;
217 | max-width: 700px;
218 | height: auto;
219 | margin-bottom: 20px;
220 | object-fit: contain;
221 | border-radius: 8px;
222 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
223 | align-self: center;
224 | }
225 |
226 |
227 |
228 | /* responsive */
229 |
230 | @media only screen and (max-width: 768px) {
231 | .container {
232 | line-height: 3rem;
233 | font-size: var(--font-size-phone);
234 | }
235 |
236 | nav {
237 | display: none;
238 | }
239 |
240 | header {
241 | font-size: var(--font-size-mid);
242 |
243 | }
244 |
245 | .code {
246 | padding: 10px 10px;
247 | font-size: var(--font-size-small);
248 | overflow-x: auto;
249 | width: 300px;
250 | line-height: var(--font-size-large);
251 | }
252 |
253 | .content {
254 | padding: 30px 0;
255 | }
256 | }
--------------------------------------------------------------------------------
/boundary/config/boundary.hcl:
--------------------------------------------------------------------------------
1 | # Copyright (c) HashiCorp, Inc.
2 | # SPDX-License-Identifier: MPL-2.0
3 |
4 | # make it false in prod
5 | disable_mlock = true
6 |
7 | controller {
8 | name = "dvh-controller"
9 | description = "A controller for dvh!"
10 | database {
11 | url = "env://BOUNDARY_PG_URL"
12 | }
13 | }
14 |
15 | worker {
16 | name = "dvh-worker"
17 | description = "A worker for a dvh project"
18 |
19 | tags {
20 | type = ["prod", "servers"]
21 | }
22 |
23 | public_addr = "192.168.1.15"
24 | }
25 |
26 | listener "tcp" {
27 | address = "boundary"
28 | purpose = "api"
29 | tls_disable = true
30 | #tls_cert_file = "/etc/boundary.d/tls/boundary-cert.pem"
31 | #tls_key_file = "/etc/boundary.d/tls/boundary-key.pem"
32 | }
33 |
34 | listener "tcp" {
35 | address = "boundary"
36 | purpose = "cluster"
37 | tls_disable = true
38 | #tls_cert_file = "/etc/boundary.d/tls/boundary-cert.pem"
39 | #tls_key_file = "/etc/boundary.d/tls/boundary-key.pem"
40 | }
41 |
42 | listener "tcp" {
43 | address = "boundary"
44 | purpose = "proxy"
45 | tls_disable = true
46 | #tls_cert_file = "/etc/boundary.d/tls/boundary-cert.pem"
47 | #tls_key_file = "/etc/boundary.d/tls/boundary-key.pem"
48 | }
49 |
50 |
51 | events {
52 | audit_enabled = false
53 | sysevents_enabled = true
54 | observations_enable = false
55 |
56 | sink "stderr" {
57 | name = "all-events"
58 | description = "All events sent to stderr"
59 | event_types = ["*"]
60 | format = "hclog-text"
61 | }
62 | sink {
63 | name = "audit-file"
64 | description = "audit events sent to a file"
65 | event_types = ["audit"]
66 | format = "cloudevents-json"
67 |
68 | file {
69 | path = "/var/log/boundary"
70 | file_name = "controller.log"
71 | }
72 |
73 | sink {
74 | name = "auth-sink"
75 | description = "Authentications sent to a file"
76 | event_types = ["observation"]
77 | format = "cloudevents-json"
78 | allow_filters = [
79 | "\"/data/request_info/path\" contains \":authenticate\""
80 | ]
81 | file {
82 | path = "/var/log/boundary"
83 | file_name = "auth.log"
84 | }
85 | }
86 |
87 | sink {
88 | name = "session-sink"
89 | description = "Authorize session requests and services sent to a file"
90 | event_types = ["audit"]
91 | format = "cloudevents-json"
92 | allow_filters = [
93 | "\"/data/request_info/path\" contains \":authorize-session\"",
94 | "\"/data/request_info/method\" contains \"SessionService\"",
95 | ]
96 | file {
97 | path = "/var/log/boundary"
98 | file_name = "sessions.log"
99 | }
100 | }
101 |
102 | audit_config {
103 | audit_filter_overrides {
104 | secret = "encrypt"
105 | sensitive = "hmac-sha256"
106 | }
107 | }
108 | }
109 | }
110 |
111 |
112 |
113 | kms "transit" {
114 | purpose = "root"
115 | address = "http://vault:8200"
116 | // token = "hvs.qtHSKlalvkOgLZKGFRmHj90v"
117 | disable_renewal = "false"
118 |
119 | // Key configuration
120 | key_name = "root-boundary"
121 | mount_path = "transit/"
122 | tls_skip_verify = "true"
123 | vault_prefix = "transit/"
124 | }
125 | kms "transit" {
126 | purpose = "worker-auth"
127 | address = "http://vault:8200"
128 | // token = "hvs.qtHSKlalvkOgLZKGFRmHj90v"
129 | disable_renewal = "false"
130 |
131 | // Key configuration
132 | key_name = "worker-auth-boundary"
133 | mount_path = "transit/"
134 | tls_skip_verify = "true"
135 | vault_prefix = "transit/"
136 | }
137 | kms "transit" {
138 | purpose = "recovery"
139 | address = "http://vault:8200"
140 | // token = "hvs.qtHSKlalvkOgLZKGFRmHj90v"
141 | disable_renewal = "false"
142 |
143 | // Key configuration
144 | key_name = "recovery-boundary"
145 | mount_path = "transit/"
146 | tls_skip_verify = "true"
147 | vault_prefix = "transit/"
148 | }
149 |
--------------------------------------------------------------------------------
/boundary/terraform/README.md:
--------------------------------------------------------------------------------
1 | # Hashicorp Boundary Provider
2 | This provider aims at creating and managing Hashicorp boundary resources using best practices.
3 |
4 | ## How To Use
5 |
6 | **First, make sure you have Boundary and Vault (transit key auth) up & running.**
7 | 1. Create a terraform.tfvars file and fill it in with optional values according to `variables.tf`
8 | 2. Run the conventional terraform commands to deploy IaC to your boundary instance
9 | ```
10 | export BOUNDARY_ADDR="https://your-boundary-instance.com"
11 | export VAULT_TOKEN="vault-transit-token"
12 |
13 | terraform init
14 | terraform fmt
15 | terraform validate
16 | terraform plan
17 | terrafrom apply
18 | ```
19 |
20 | **Replace `vault-transit-token` and `https://your-boundary-instance.com` with your actual values.**
21 |
22 | ## Bear In Mind
23 | **Using the `Vault Transit recovery key` for auth with Terraform is a best practice, you can create a password auth method manually ON BOUNDARY UI and then `auth_method` related lines in `provider.tf`; This way you won't necessarily need Vault to use this template.**
24 |
25 | - Breaking down resources in `main.tf` into separate tf files is also a plus point for better readability.
26 |
27 | - Some features like Credential Injection and Session recording solely come by the paid plan. so uncomment them as you wish in `main.tf`.
28 |
29 | - If you're utilizing Vault recovery key ensure `recovery_kms_hcl` directives in `provider.tf` to match your Vault transit engine credentials.
--------------------------------------------------------------------------------
/boundary/terraform/main.tf:
--------------------------------------------------------------------------------
1 | # data sources
2 | data "boundary_auth_method" "auth_method" {
3 | name = "password"
4 | }
5 |
6 | data "boundary_user" "global_scope_admin" {
7 | name = "admin"
8 | }
9 | #----------
10 |
11 | resource "boundary_scope" "global" {
12 | name = "Global"
13 | global_scope = true
14 | description = "highest-level Scope for administrators"
15 | scope_id = "global"
16 | }
17 |
18 | resource "boundary_scope" "corp" {
19 | name = "DVH"
20 | description = "DVH scope"
21 | scope_id = boundary_scope.global.id
22 | auto_create_admin_role = true
23 | auto_create_default_role = true
24 | }
25 |
26 | resource "boundary_scope" "core_infra" {
27 | name = "tf-templates"
28 | description = "DVH delivery project"
29 | scope_id = boundary_scope.corp.id
30 | auto_create_admin_role = true
31 | }
32 |
33 | ## Use password auth method
34 | resource "boundary_auth_method" "password" {
35 | name = "DVH Password"
36 | scope_id = boundary_scope.corp.id
37 | type = "password"
38 | }
39 |
40 | ## users and passwords
41 | resource "boundary_account_password" "client_acct" {
42 | for_each = var.clients
43 | name = each.key
44 | description = "User account for ${each.key}"
45 | # type = "password"
46 | login_name = lower(each.key)
47 | password = each.value
48 | auth_method_id = boundary_auth_method.password.id
49 | }
50 | resource "boundary_account_password" "admins_acct" {
51 | for_each = var.admins
52 | name = "${each.key} account"
53 | description = "User account for ${each.key}"
54 | # type = "password"
55 | login_name = lower(each.key)
56 | password = each.value
57 | auth_method_id = boundary_auth_method.password.id
58 | }
59 |
60 | resource "boundary_account_password" "readonly_users_acct" {
61 | for_each = var.readonly_users
62 | name = each.key
63 | description = "User account for ${each.key}"
64 | # type = "password"
65 | login_name = lower(each.key)
66 | password = each.value
67 | auth_method_id = boundary_auth_method.password.id
68 | }
69 |
70 | resource "boundary_user" "clients" {
71 | for_each = var.clients
72 | name = each.key
73 | description = "User resource for ${each.key}"
74 | scope_id = boundary_scope.corp.id
75 | account_ids = [boundary_account_password.client_acct[each.key].id]
76 | }
77 | resource "boundary_user" "admins" {
78 | for_each = var.admins
79 | name = each.key
80 | description = "User resource for ${each.key}"
81 | scope_id = boundary_scope.corp.id
82 | account_ids = [boundary_account_password.admins_acct[each.key].id]
83 | }
84 | ### readonly users
85 | resource "boundary_user" "readonly_users" {
86 | for_each = var.readonly_users
87 | name = each.key
88 | description = "User resource for ${each.key}"
89 | scope_id = boundary_scope.corp.id
90 | account_ids = [for account in boundary_account_password.readonly_users_acct : account.id]
91 | }
92 |
93 | ## groups and roles
94 | resource "boundary_group" "readonly_group" {
95 | name = "read-only"
96 | description = "Organization group for readonly users"
97 | member_ids = [for user in boundary_user.readonly_users : user.id]
98 | scope_id = boundary_scope.corp.id
99 | }
100 | resource "boundary_group" "clients_group" {
101 | name = "clients-group"
102 | description = "Organization group for regular users"
103 | member_ids = [for user in boundary_user.clients : user.id]
104 | scope_id = boundary_scope.corp.id
105 | }
106 | resource "boundary_group" "admin_group" {
107 | name = "admins-group"
108 | description = "Organization group for regular users"
109 | member_ids = [for user in boundary_user.admins : user.id]
110 | scope_id = boundary_scope.corp.id
111 | }
112 |
113 | resource "boundary_role" "dvh_readonly" {
114 | name = "Read-only"
115 | description = "Read-only role"
116 | principal_ids = [boundary_group.readonly_group.id]
117 | grant_strings = [var.all_list]
118 | scope_id = boundary_scope.core_infra.id
119 | }
120 |
121 | resource "boundary_role" "dvh_admin" {
122 | name = "admin"
123 | description = "Administrator role"
124 | principal_ids = [boundary_group.admin_group.id]
125 | grant_strings = [var.all_actions]
126 | scope_id = boundary_scope.core_infra.id
127 | }
128 |
129 | resource "boundary_role" "dvh_client" {
130 | name = "client"
131 | description = "client role for mereley connecting through boudary."
132 | principal_ids = [boundary_group.clients_group.id]
133 | grant_strings = [var.all_list, var.target_authorize_session, var.auth_method_authenticate]
134 | scope_id = boundary_scope.core_infra.id
135 | }
136 |
137 |
138 | ## Main servers Targets configuration
139 | resource "boundary_host_catalog_static" "main_servers" {
140 | name = "main_servers"
141 | description = "main servers host catalog"
142 | scope_id = boundary_scope.core_infra.id
143 | }
144 |
145 | resource "boundary_host_static" "main_servers" {
146 | for_each = { for host in var.hosts_info : host.name => host }
147 | type = "static"
148 | name = each.value.name
149 | description = "Main server host"
150 | address = each.value.ip
151 | host_catalog_id = boundary_host_catalog_static.main_servers.id
152 | }
153 |
154 | resource "boundary_host_set_static" "main_servers_ssh" {
155 | type = "static"
156 | name = "main_servers_ssh"
157 | description = "Host set for main servers"
158 | host_catalog_id = boundary_host_catalog_static.main_servers.id
159 | host_ids = [for host in boundary_host_static.main_servers : host.id]
160 | }
161 |
162 |
163 | ## credential stores
164 | resource "boundary_credential_store_static" "main_cred_store" {
165 | name = var.main_cred_store_name
166 | scope_id = boundary_scope.core_infra.id
167 | }
168 |
169 | resource "boundary_credential_ssh_private_key" "main_server_keys" {
170 | for_each = { for host in var.hosts_info : host.name => host }
171 | name = each.value.ssh_key_name
172 | username = sensitive(each.value.ssh_user)
173 | credential_store_id = boundary_credential_store_static.main_cred_store.id
174 | private_key = file(each.value.ssh_key_path)
175 | }
176 |
177 | # ssh target for DVH main servers
178 | resource "boundary_target" "main_servers_ssh" {
179 | type = "tcp"
180 | for_each = { for host in var.hosts_info : host.name => host }
181 | name = "${each.value.name}_server"
182 | description = "Main servers SSH target"
183 | scope_id = boundary_scope.core_infra.id
184 | brokered_credential_source_ids = [for credential in boundary_credential_ssh_private_key.main_server_keys : credential.id]
185 | default_port = each.value.ssh_port
186 |
187 | host_source_ids = [
188 | boundary_host_set_static.main_servers_ssh.id
189 | ]
190 | }
191 |
192 | #### Start of Vault ssh credential store
193 | # The following resources are only applied if SSH_INJECTION is set to True.
194 |
195 | resource "boundary_credential_store_vault" "vault_cert_store" {
196 | count = var.SSH_INJECTION ? 1 : 0
197 | name = "vault-cred-store"
198 | address = var.vault_address
199 | token = var.vault_cred_store_token
200 | scope_id = boundary_scope.core_infra.id
201 | }
202 |
203 | resource "boundary_credential_library_vault_ssh_certificate" "vault_cred_lib_ssh" {
204 | count = var.SSH_INJECTION ? 1 : 0
205 | name = "certificates-library"
206 | credential_store_id = boundary_credential_store_vault.vault_cert_store[count.index].id
207 | path = var.vault_sign_path
208 | username = var.vault_username
209 | key_type = "ecdsa"
210 | key_bits = 521
211 | }
212 |
213 | resource "boundary_target" "test_server_ssh" {
214 | count = var.SSH_INJECTION ? 1 : 0
215 | type = "tcp"
216 | name = "${var.test_server_name}_ssh_server"
217 | description = "test servers SSH target"
218 | scope_id = boundary_scope.core_infra.id
219 | default_port = var.test_ssh_port
220 | injected_application_credential_source_ids = [boundary_credential_library_vault_ssh_certificate.vault_cred_lib_ssh[count.index].id]
221 | host_source_ids = [
222 | boundary_host_set_static.main_servers_ssh.id
223 | ]
224 | }
225 | ### End of cred injection
226 |
227 | # TODO: break resources into seperate tf files
--------------------------------------------------------------------------------
/boundary/terraform/output.tf:
--------------------------------------------------------------------------------
1 | output "ssh_key_ids" {
2 | value = "IDs for ssh_private keys: ${join(", ", values(boundary_credential_ssh_private_key.main_server_keys)[*].id)}"
3 | description = "show the id of ssh private key"
4 | }
5 |
6 | output "auth_method_id" {
7 | value = "BOUNDARY_AUTH_METHOD_ID: ${data.boundary_auth_method.auth_method.id}"
8 | description = "BOUNDARY_AUTH_METHOD_ID value!"
9 | }
10 |
11 | output "target_id" {
12 | value = "main servers TARGET_ID for staging, production: ${join(", ", values(boundary_target.main_servers_ssh)[*].id)}"
13 | description = "show the id of ssh private key"
14 | }
15 |
16 | output "scope_id" {
17 | value = "the scope id for admins: ${boundary_scope.corp.scope_id}"
18 | description = "show the id of ssh private key"
19 | }
20 |
21 | output "boundary_address" {
22 | value = "BOUNDARY_ADDR : ${var.boundary_address}"
23 | description = "the address that boundary is serving at!"
24 | sensitive = true
25 | }
26 |
27 |
28 | output "user_ids" {
29 | value = "admin user_ids is ${data.boundary_user.global_scope_admin.id} and admin username is ${data.boundary_user.global_scope_admin.name}"
30 | }
31 |
--------------------------------------------------------------------------------
/boundary/terraform/provider.tf:
--------------------------------------------------------------------------------
1 | provider "boundary" {
2 | addr = var.boundary_address
3 | # auth_method_id = var.auth_method_id
4 | # auth_method_login_name = var.login_name
5 | # auth_method_password = var.login_password
6 |
7 | ## Address is the VAULT SERVER ADDRESS
8 | recovery_kms_hcl = <> $tfvars_file
19 | return 0
20 | }
21 |
22 | function delete_token(){
23 | sed -i '/^transit_token/d' $var_file
24 | }
25 |
26 | if [[ $1 == "-d" ]]; then
27 | delete_token
28 | elif [[ $1 == "ssh" ]];then
29 | inject_ssh_cred
30 | fi
31 |
32 | if grep "transit_token" $var_file &> /dev/null; then
33 | delete_token
34 | fi
35 | echo -e "\ntransit_token: $token" >> $var_file
--------------------------------------------------------------------------------
/scripts/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | source "${STACK_DIR}/scripts/linter.sh"
4 |
5 | export COMPOSE_DIR="${STACK_DIR}/deploy"
6 |
7 | function boundary() {
8 |
9 | export VAULT_TOKEN=${VAULT_TOKEN}
10 | export VAULT_ADDR=${VAULT_ADDR}
11 |
12 | lint_docker
13 |
14 | if [[ "$STACK_INIT" == "false" ]]; then
15 | docker compose -f "${COMPOSE_DIR}/boundary.yml" up -d postgres_db boundary wait
16 | return $?
17 | fi
18 | docker compose -f "${COMPOSE_DIR}/boundary.yml" up -d
19 |
20 | return 0
21 | }
22 |
23 | function vault() {
24 |
25 | lint_docker
26 |
27 | if [[ "$STACK_INIT" == "false" ]]; then
28 | docker compose -f "${COMPOSE_DIR}/vault.yml" up -d vault
29 | return $?
30 | fi
31 |
32 | docker compose -f "${COMPOSE_DIR}/vault.yml" up -d
33 |
34 | return 0
35 | }
36 |
37 |
38 | if [[ "$1" == "boundary" ]]; then
39 | boundary
40 | elif [[ "$1" == "vault" ]]; then
41 | vault
42 | else
43 | echo "ERROR : Pleasae provide the service name you want to deploy" >&2
44 | exit 4
45 | fi
--------------------------------------------------------------------------------
/scripts/init.sh:
--------------------------------------------------------------------------------
1 | # !/bin/env bash
2 |
3 | set -e -o pipefail
4 |
5 | function usage() {
6 | cat <
9 |
10 | Description:
11 | This script inits boundary/vault setup/IaC (terraform).
12 |
13 | Options:
14 | -h, --help Display this help message and exit
15 |
16 | Arguments:
17 | boundary Apply terrform configurations for boundary
18 | vault Apply terrform configurations for vault
19 | path/to/basedir Choose a base dir for the project (optional)
20 |
21 | Example:
22 |
23 | - init vault:
24 | $(basename "$0") vault /PATH/TO/BASEDIR
25 |
26 | - init boundary:
27 | $(basename "$0") boundary /PATH/TO/BASEDIR
28 |
29 |
30 | EOF
31 | }
32 |
33 | if [[ $# -eq 0 ]]; then
34 | usage
35 | exit 0
36 | fi
37 |
38 | # source into linter script
39 | if [[ $1 != "vault-init" ]]; then
40 | source "${HOME_DIR}/scripts/linter.sh"
41 | fi
42 |
43 | STACK_DIR="${2:-/home/ubuntu/boundary-vault-stack}"
44 |
45 | # this function is for dev env
46 | function init_compose(){
47 | cd compose/
48 |
49 | lint_docker
50 |
51 | docker-compose -f docker-compose.yml up -d
52 | }
53 |
54 | function init_boundary_iac(){
55 | export HOME_DIR="${HOME_DIR}"
56 |
57 | cd "${HOME_DIR}/boundary/terraform/"
58 | lint_terraform
59 |
60 | secret_file="${HOME_DIR}/secrets/secrets.txt"
61 | token=$(cat $secret_file | grep "transit-token" | awk '{print $2}')
62 | export VAULT_TOKEN="$token"
63 | export BOUNDARY_ADDR="$BOUNDARY_ADDR"
64 | # ssh_injection=$(echo "$SSH_INJECTION" | tr '[:upper:]' '[:lower:]')
65 | # export TF_VAR_SSH_INJECTION=$ssh_injection
66 |
67 | log_path="${HOME_DIR}/logs/terraform/boundary-logs.txt"
68 | if ! terraform plan &> /dev/null; then
69 | echo -e "Terraform Plan failed for Vault, check the logs at $log_path \n\n" >&2
70 | fi
71 | terraform apply --auto-approve 2>&1 | sed -r "s/\x1B\[[0-9;]*[mGKH]//g" > $log_path
72 | }
73 |
74 | function init_vault_iac(){
75 | export HOME_DIR="${HOME_DIR}"
76 |
77 | cd "${HOME_DIR}/vault/terraform"
78 | lint_terraform
79 |
80 | secret_file="${HOME_DIR}/secrets/secrets.txt"
81 | root_token="$(cat $secret_file | head -n1 | awk '{print $8}')"
82 | export VAULT_TOKEN="$root_token"
83 |
84 | log_path="${HOME_DIR}/logs/terraform/vault-logs.txt"
85 | if ! terraform plan &> /dev/null ;then
86 | echo -e "Terraform Plan failed for Vault, check the logs at $log_path \n\n" >&2
87 | fi
88 | terraform apply --auto-approve 2>&1 | sed -r "s/\x1B\[[0-9;]*[mGKH]//g" > $log_path
89 | }
90 |
91 | ## vault init container to setup vault and get killed right after.
92 | function init_vault_setup(){
93 | secret_file="/home/secrets.txt"
94 |
95 | export VAULT_ADDR='http://vault:8200'
96 |
97 | ### initializing and unsealing vault
98 | init_secrets="$(vault operator init -key-shares=1 -key-threshold=1)"
99 |
100 | unseal_key="$(echo $init_secrets | grep -i Unseal | awk '{print $4}')"
101 | root_token="$(echo $init_secrets | awk '{print $8}')"
102 |
103 | export UNSEAL_KEY="$unseal_key"
104 |
105 | echo $init_secrets > $secret_file
106 |
107 | vault operator unseal $UNSEAL_KEY
108 |
109 | echo -n "$root_token" | vault login -
110 |
111 | vault audit enable file file_path="/vault/logs/vault_audit.log"
112 | vault write sys/quotas/config enable_rate_limit_audit_logging=true
113 |
114 |
115 | ### add transit engine and apply rate-limit
116 | # vault write sys/quotas/rate-limit/transit-limit \
117 | # path="transit" \
118 | # rate=10 \
119 | # interval=60
120 |
121 | ### configure boundary ssh brokering
122 | # enable ssh secret engine
123 | vault secrets enable -path=ssh-signer ssh
124 |
125 | export CRED_STORE_TOKEN=$(vault token create \
126 | -no-default-policy=true \
127 | -policy="boundary-controller" \
128 | -policy="ssh" \
129 | -orphan=true \
130 | -period=24h \
131 | -renewable=true | grep "hvs" | awk '{print $2}')
132 |
133 | vault write --format=json ssh-signer/config/ca generate_signing_key=true | grep -o '"public_key":.*' | awk -F'"' '{print $4}' \
134 | > /home/ca-key.pub
135 |
136 | transit_token=$(vault token create \
137 | -policy="boundary-transit" \
138 | | grep "hvs" | awk '{print $2}')
139 | echo -e "\ntransit-token $transit_token \nvault_cred_store_token \"${CRED_STORE_TOKEN}\"" >> $secret_file
140 | }
141 |
142 | export VAULT_ADDR="$VAULT_ADDR"
143 |
144 | if [[ $1 == "vault" ]]; then
145 |
146 | init_vault_iac
147 |
148 | exit 0
149 | fi
150 |
151 | if [[ $1 == "boundary" ]]; then
152 |
153 | init_boundary_iac
154 | exit 0
155 | fi
156 |
157 | if [[ $1 == "vault-init" ]]; then
158 | init_vault_setup
159 | exit 0
160 | fi
161 |
162 | if [[ "$1" == "-h" || $1 == "--help" ]]; then
163 | usage
164 | exit 0
165 | fi
166 |
167 |
168 |
--------------------------------------------------------------------------------
/scripts/install_services.sh:
--------------------------------------------------------------------------------
1 |
2 | if ! command -v docker &> /dev/null; then
3 | sudo apt-get update
4 | sudo apt install ca-certificates curl
5 | sudo install -m 0755 -d /etc/apt/keyrings
6 | sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
7 | sudo chmod a+r /etc/apt/keyrings/docker.asc
8 |
9 | # Add the repository to Apt sources:
10 | echo \
11 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
12 | $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
13 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
14 |
15 | sudo apt update
16 |
17 | sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose
18 |
19 | sudo usermod -aG docker vagrant
20 |
21 | newgrp docker
22 |
23 | fi
24 |
25 | if ! command -v terraform &> /dev/null; then
26 |
27 | sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
28 | wget -O- https://apt.releases.hashicorp.com/gpg | \
29 | gpg --dearmor | \
30 | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
31 |
32 | gpg --no-default-keyring \
33 | --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \
34 | --fingerprint
35 | echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
36 | https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
37 | sudo tee /etc/apt/sources.list.d/hashicorp.list
38 | sudo apt update
39 | sudo apt-get install terraform
40 | fi
41 | exit 0
--------------------------------------------------------------------------------
/scripts/install_vagrant.sh:
--------------------------------------------------------------------------------
1 | wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg &&
2 | echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list &&
3 | sudo apt-get update && sudo apt install vagrant
--------------------------------------------------------------------------------
/scripts/install_virtual_box.sh:
--------------------------------------------------------------------------------
1 | sudo apt-get install virtualbox
--------------------------------------------------------------------------------
/scripts/linter.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | function lint_terraform(){
6 |
7 | if ! command -v terraform &> /dev/null; then
8 | echo -e "ERROR: Terraform is not installed!" >&2
9 | echo -e "Please install Terraform from https://www.terraform.io/downloads.html" >&2
10 | return 1
11 | fi
12 |
13 | if ! terraform init &> /dev/null; then
14 | echo "ERROR: Terraform init failed!" >&2
15 | echo "Ensure you are in the correct directory and check the Terraform configuration." >&2
16 | return 2
17 | fi
18 |
19 | if ! terraform validate &> /dev/null; then
20 | echo "ERROR: Terraform configuration is not valid!" >&2
21 | echo "Check the output above for details on what went wrong." >&2
22 | return 3
23 | fi
24 |
25 | return 0
26 | }
27 |
28 | function lint_docker () {
29 |
30 | if ! command -v docker &> /dev/null; then
31 | echo -e "ERROR: Docker is not installed!" >&2
32 | echo -e "Please install Docker from https://docs.docker.com/get-docker/" >&2
33 | return 1
34 | fi
35 |
36 | if ! docker compose version &> /dev/null; then
37 | echo "WARNING: Docker Version is not 2+"
38 | fi
39 |
40 | if ! docker-compose --version &> /dev/null; then
41 | echo "ERROR: Docker Compose is not installed!" >&2
42 | echo "Please install Docker Compose from https://docs.docker.com/compose/install/" >&2
43 | return 1
44 | fi
45 |
46 | return 0
47 |
48 | }
49 |
50 | function lint_vagrant(){
51 | if ! command -v vagrant &> /dev/null; then
52 | echo -e "ERROR: Vagrant is not installed!" >&2
53 | echo -e "Please install Vagrant from https://developer.hashicorp.com/vagrant/downloads" >&2
54 | return 1
55 | fi
56 |
57 | if ! command -v VBoxManage &> /dev/null; then
58 | echo -e "ERROR: VirtualBox is not installed \nVagrant uses Virtualbox to provision vms." >&2
59 | echo -e "Please install VirtualBox from https://virtualbox.org/wiki/Linux_Downloads" >&2
60 | return 1
61 | fi
62 | return 0
63 | }
64 |
65 | function lint_py(){
66 | if ! command -v python3 && ! command -v python ; then
67 | echo "Error: Python Is Not Installed." >&2
68 | return 1
69 | fi
70 | return 0
71 | }
72 |
73 | function lint_ansible () {
74 |
75 | cd ../ansible || { echo "Failed to change directory to ansible"; return 1; }
76 |
77 | playbooks=$(find . -maxdepth 1 -name "*.yml" -print)
78 | for play in $playbooks; do
79 | if ! ansible-playbook $play --syntax-check &> /dev/null; then
80 | echo "Ansible Syntax Error: syntax check failed for $play, check the underlying roles!"
81 | return 3
82 | fi
83 | echo "$play is fine in terms of syntax!"
84 | done
85 | return 0
86 | }
87 |
88 | if [[ $1 == "ansible" ]]; then
89 | lint_ansible
90 | fi
--------------------------------------------------------------------------------
/scripts/pull-images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e -o pipefail
3 |
4 | GREEN='\033[0;32m'
5 | RED='\033[0;31m'
6 | RESET='\033[0m'
7 |
8 | images=("hashicorp/vault:1.13.3" "bitnami/postgresql:16" "hashicorp/boundary:0.14" "busybox:latest")
9 |
10 | for image_name in "${images[@]}"; do
11 | docker pull $image_name &> /dev/null
12 | if [ $? -ne 0 ]; then
13 | echo -e "${RED}ERROR : failed to pull image : $image_name${RESET}" >&2
14 | exit 1
15 | fi
16 | echo -e "${GREEN}downloaded $image_name ${RESET}\n" >&2
17 | done
18 | echo -e "All images got pulled successfully"
19 | exit 0
20 |
--------------------------------------------------------------------------------
/scripts/tls-gen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/env sh
2 |
3 | set -e
4 | STACK_DIR="/home/ubuntu/boundary-vault-stack"
5 | key_dir="${STACK_DIR}/keys"
6 |
7 | service="boundary"
8 |
9 | openssl genpkey -algorithm RSA -out "${key_dir}/${service}/${service}-key.pem" -aes256
10 |
11 | openssl req -new -key "${key_dir}/${service}/${service}-key.pem "-out "${key_dir}/${service}/${service}-csr.pem"
12 |
13 | openssl x509 -req -days 365 -in "${key_dir}/${service}/${service}-csr.pem" -signkey "${key_dir}/$service/${service}-key.pem" -out "${key_dir}/${service}/${service}-cert.pem"
14 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | function usage(){
6 |
7 | cat <
10 |
11 | Description:
12 | This script initiates boundary vault stack in your desired environment.
13 |
14 | Options:
15 | --help Display this help message and exit
16 | -e Define which environment to run the stack in (development/staging/test/production).
17 |
18 | Example:
19 |
20 | - run in dev:
21 | $(basename "$0") -e development
22 |
23 | EOF
24 | }
25 |
26 | if [[ $1 == "--help" ]]; then
27 | usage
28 | exit 0
29 | fi
30 |
31 | function check_env(){
32 | case "$1" in
33 | development | staging | test | production)
34 | return 0
35 | ;;
36 | *)
37 | echo "ERROR: Invalid value for -e : ${1} , For more info use --help or reach out to Documentation." >&2
38 | return 4
39 | ;;
40 | esac
41 | }
42 |
43 | if [ -z "$STACK_ENV" ]; then
44 | while getopts "e:" opt; do
45 | case $opt in
46 | e)
47 | check_env "$OPTARG"
48 | export STACK_ENV="${OPTARG}"
49 | echo "Running Boundary Vault Stack on ${STACK_ENV} Mode."
50 | ;;
51 | \?)
52 | echo "ERROR: Invalid option: -${OPTARG} For more info use --help or reach out to Documentation" >&2
53 | return 4
54 | ;;
55 | esac
56 | done
57 | elif ! check_env "$STACK_ENV"; then
58 | exit 4
59 | fi
60 |
61 | if [ $# -ne 2 ]; then
62 | usage
63 | exit 4
64 | fi
65 |
66 |
67 | echo -e "***Running Boundary Vault Stack on ${STACK_ENV} Mode.****\n"
68 |
69 |
70 | ## create ignored dirs in git for confidential data
71 | mkdir -p logs/ logs/docker logs/terraform secrets/
72 |
73 | source ./scripts/linter.sh
74 | if [[ ! -d "venv/" ]]; then
75 | echo -e "\nInstalling Virtual Env and dependencies."
76 |
77 | py_cmd=$(lint_py)
78 | $py_cmd -m venv venv
79 | source venv/bin/activate
80 | pip install -U pip
81 | pip install -r ./requirements.txt
82 | else
83 | source venv/bin/activate
84 | pip install -r ./requirements.txt
85 | fi
86 |
87 | ## install required collections
88 | #!/bin/bash
89 |
90 | # Check if Vagrant is installed
91 | if command -v vagrant &> /dev/null
92 | then
93 | echo "Vagrant is installed"
94 | vagrant --version
95 | else
96 | sh scripts/install_vagrant.sh
97 | fi
98 | #install virtualbox
99 | sh scripts/install_virtual_box.sh
100 |
101 | ansible-galaxy collection install -r requirements.yml
102 |
103 | # provision the server
104 | if [ -z "$STACK_SERVER"]; then
105 | lint_vagrant
106 | vagrant up
107 | fi
108 |
109 | ansible-playbook -i ansible/inventory/inventory.ini ansible/playbook.yml --ask-vault-pass
110 | echo "****** Applying Vault changes ******"
111 | sleep 10
112 | ansible-playbook -i ansible/inventory/inventory.ini ansible/terraform.yml --ask-vault-pass
113 | echo "********* Applying terraform provisioning ******* "
114 | sleep 5
115 | ansible-playbook -i ansible/inventory/inventory.ini ansible/boundary.yml --ask-vault-pass
116 |
117 | echo "***** Performing Stack Cleanup *******"
118 | ansible-playbook -i ansible/inventory/inventory.ini ansible/cleanup.yml --ask-vault-pass
--------------------------------------------------------------------------------
/vagrantfile:
--------------------------------------------------------------------------------
1 | # Vagrantfile for deploying a VM to VirtualBox with specified configurations
2 |
3 | Vagrant.configure("2") do |config|
4 | # Define the base box
5 | config.vm.box = "ubuntu/focal64" # or another box of your choice
6 |
7 | # Set the name of the VM
8 | config.vm.define "bvstack" do |node|
9 | node.vm.host_name = "bvstack"
10 |
11 | # Network configuration: Bridge
12 | node.vm.network "public_network", ip: "192.168.1.15"
13 |
14 | # Resources: CPU and RAM
15 | node.vm.provider "virtualbox" do |vb|
16 | vb.name = "bvstack"
17 | vb.memory = 4096
18 | vb.cpus = 2
19 | end
20 | end
21 |
22 | config.vm.provision "file", source: "~/.ssh/id_rsa.pub" , destination:"~/.ssh/id_rsa.pub"
23 | end
24 |
--------------------------------------------------------------------------------
/vault/config/vault.hcl:
--------------------------------------------------------------------------------
1 | listener "tcp" {
2 | address = "0.0.0.0:8200"
3 | tls_disable = true
4 | #tls_cert_file = "/path/to/fullchain.pem"
5 | #tls_key_file = "/path/to/privkey.pem"
6 | }
7 |
8 | storage "raft" {
9 | path = "/vault/data"
10 | # maximum record/entry vault accepts from clients
11 | max_entry_size = 1048576
12 | node_id = "node1"
13 | }
14 |
15 | # login limit for users
16 |
17 | user_lockout "userpass" {
18 | lockout_threshold = "3"
19 | lockout_duration = "10m"
20 | }
21 |
22 | api_addr = "http://localhost:8200"
23 | cluster_addr = "http://127.0.0.1:8201"
24 | ui = true
--------------------------------------------------------------------------------
/vault/policy/kv-policy.hcl:
--------------------------------------------------------------------------------
1 | # vault initial policy file
2 |
3 | path "kee/*" {
4 | capabilities = ["read"]
5 | }
6 |
7 | path "kv/*" {
8 | capabilities = ["read"]
9 | }
--------------------------------------------------------------------------------
/vault/terraform/auth.tf:
--------------------------------------------------------------------------------
1 | #--------------------------------
2 | # Enable userpass auth method
3 | #--------------------------------
4 |
5 | resource "vault_auth_backend" "userpass" {
6 | type = "userpass"
7 | path = var.userpass_path
8 | }
9 |
10 | # Create Admin users
11 | resource "vault_generic_endpoint" "admins" {
12 | for_each = var.login_creds
13 | depends_on = [vault_auth_backend.userpass]
14 | path = "auth/${var.userpass_path}/users/${each.key}"
15 | ignore_absent_fields = true
16 |
17 | data_json = <