mirror of https://github.com/trailofbits/algo
Merge branch 'master' into master
commit
90aa093bef
@ -1,3 +1,10 @@
|
|||||||
skip_list:
|
skip_list:
|
||||||
|
- yaml
|
||||||
- '204'
|
- '204'
|
||||||
verbosity: 1
|
verbosity: 1
|
||||||
|
|
||||||
|
warn_list:
|
||||||
|
- no-changed-when
|
||||||
|
- no-handler
|
||||||
|
- fqcn-builtins
|
||||||
|
- var-spacing
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Maintain dependencies for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|
||||||
|
# Maintain dependencies for Python
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
@ -0,0 +1,44 @@
|
|||||||
|
name: Create and publish a Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['master']
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
# set latest tag for master branch
|
||||||
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
@ -0,0 +1 @@
|
|||||||
|
* @jackivanov
|
@ -0,0 +1,9 @@
|
|||||||
|
# Reporting Security Issues
|
||||||
|
|
||||||
|
The Algo team and community take security bugs in Algo seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||||
|
|
||||||
|
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/trailofbits/algo/security/) tab.
|
||||||
|
|
||||||
|
The Algo team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||||
|
|
||||||
|
Report security bugs in third-party modules to the person or team maintaining the module.
|
@ -0,0 +1,36 @@
|
|||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
config.vm.box = "bento/ubuntu-20.04"
|
||||||
|
|
||||||
|
config.vm.provider "virtualbox" do |v|
|
||||||
|
v.name = "algo-20.04"
|
||||||
|
v.memory = "512"
|
||||||
|
v.cpus = "1"
|
||||||
|
end
|
||||||
|
|
||||||
|
config.vm.synced_folder "./", "/opt/algo", create: true
|
||||||
|
|
||||||
|
config.vm.provision "ansible_local" do |ansible|
|
||||||
|
ansible.playbook = "/opt/algo/main.yml"
|
||||||
|
|
||||||
|
# https://github.com/hashicorp/vagrant/issues/12204
|
||||||
|
ansible.pip_install_cmd = "sudo apt-get install -y python3-pip python-is-python3 && sudo ln -s -f /usr/bin/pip3 /usr/bin/pip"
|
||||||
|
ansible.install_mode = "pip_args_only"
|
||||||
|
ansible.pip_args = "-r /opt/algo/requirements.txt"
|
||||||
|
ansible.inventory_path = "/opt/algo/inventory"
|
||||||
|
ansible.limit = "local"
|
||||||
|
ansible.verbose = "-vvvv"
|
||||||
|
ansible.extra_vars = {
|
||||||
|
provider: "local",
|
||||||
|
server: "localhost",
|
||||||
|
ssh_user: "",
|
||||||
|
endpoint: "127.0.0.1",
|
||||||
|
ondemand_cellular: true,
|
||||||
|
ondemand_wifi: false,
|
||||||
|
dns_adblocking: true,
|
||||||
|
ssh_tunneling: true,
|
||||||
|
store_pki: true,
|
||||||
|
tests: true,
|
||||||
|
no_log: false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,88 @@
|
|||||||
|
# Using Router with OpenWRT as a Client with WireGuard
|
||||||
|
This scenario is useful in case you want to use vpn with devices which has no vpn capability like smart tv, or make vpn connection available via router for multiple devices.
|
||||||
|
This is a tested, working scenario with following environment:
|
||||||
|
|
||||||
|
- algo installed ubuntu at digitalocean
|
||||||
|
- client side router "TP-Link TL-WR1043ND" with openwrt ver. 21.02.1. [Openwrt Install instructions](https://openwrt.org/toh/tp-link/tl-wr1043nd)
|
||||||
|
- or client side router "TP-Link Archer C20i AC750" with openwrt ver. 21.02.1. [Openwrt install instructions](https://openwrt.org/toh/tp-link/archer_c20i)
|
||||||
|
see compatible device list at https://openwrt.org/toh/start . Theoretically any of the device on list should work
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Router setup
|
||||||
|
Make sure that you have
|
||||||
|
- router with openwrt installed,
|
||||||
|
- router is connected to internet,
|
||||||
|
- router and device in front of router does not have same ip . By default openwrt have 192.168.1.1 if so change it to something like 192.168.2.1
|
||||||
|
### Install required packages(WebUI)
|
||||||
|
- Open router web UI (mostly http://192.168.1.1 )
|
||||||
|
- Login. (by default username: root, password:<empty>
|
||||||
|
- System -> Software, click "Update lists"
|
||||||
|
- Install following packages wireguard-tools, kmod-wireguard, luci-app-wireguard, wireguard, kmod-crypto-sha256, kmod-crypto-sha1, kmod-crypto-md5
|
||||||
|
- restart router
|
||||||
|
|
||||||
|
### Alternative Install required packages(ssh)
|
||||||
|
- Open router web UI (mostly http://192.168.1.1 )
|
||||||
|
- ssh root@192.168.1.1
|
||||||
|
- opkg update
|
||||||
|
- opkg install wireguard-tools, kmod-wireguard, luci-app-wireguard, wireguard, kmod-crypto-sha256, kmod-crypto-sha1, kmod-crypto-md5
|
||||||
|
- reboot
|
||||||
|
|
||||||
|
### Create an Interface(WebUI)
|
||||||
|
- Open router web UI
|
||||||
|
- Navigate Network -> Interface
|
||||||
|
- Click "Add new interface"
|
||||||
|
- Give a Name. e.g. `AlgoVpn`
|
||||||
|
- Select Protocol. `Wireguard VPN`
|
||||||
|
- click `Create Interface`
|
||||||
|
- In *General Settings* tab
|
||||||
|
- `Bring up on boot` *checked*
|
||||||
|
- Private key: `Interface -> Private Key` from algo config file
|
||||||
|
- Ip Address: `Interface -> Address` from algo config file
|
||||||
|
- In *Peers* tab
|
||||||
|
- Click add
|
||||||
|
- Name `algo`
|
||||||
|
- Public key: `[Peer]->PublicKey` from algo config file
|
||||||
|
- Preshared key: `[Peer]->PresharedKey` from algo config file
|
||||||
|
- Allowed IPs: 0.0.0.0/0
|
||||||
|
- Route Allowed IPs: checked
|
||||||
|
- Endpoint Host: `[Peer]->Endpoint` ip from algo config file
|
||||||
|
- Endpoint Port: `[Peer]->Endpoint` port from algo config file
|
||||||
|
- Persistent Keep Alive: `25`
|
||||||
|
- Click Save & Save Apply
|
||||||
|
|
||||||
|
### Configure Firewall(WebUI)
|
||||||
|
- Open router web UI
|
||||||
|
- Navigate to Network -> Firewall
|
||||||
|
- Click `Add configuration`:
|
||||||
|
- Name: e.g. ivpn_fw
|
||||||
|
- Input: Reject
|
||||||
|
- Output: Accept
|
||||||
|
- Forward: Reject
|
||||||
|
- Masquerading: Checked
|
||||||
|
- MSS clamping: Checked
|
||||||
|
- Covered networks: Select created VPN interface
|
||||||
|
- Allow forward to destination zones - Unspecified
|
||||||
|
- Allow forward from source zones - lan
|
||||||
|
- Click Save & Save Apply
|
||||||
|
- Reboot router
|
||||||
|
|
||||||
|
|
||||||
|
There may be additional configuration required depending on environment like dns configuration.
|
||||||
|
|
||||||
|
You can also verify the configuration using ssh. /etc/config/network. It should look like
|
||||||
|
|
||||||
|
```
|
||||||
|
config interface 'algo'
|
||||||
|
option proto 'wireguard'
|
||||||
|
list addresses '10.0.0.2/32'
|
||||||
|
option private_key '......' # The private key generated by itself just now
|
||||||
|
|
||||||
|
config wireguard_wg0
|
||||||
|
option public_key '......' # Server's public key
|
||||||
|
option route_allowed_ips '1'
|
||||||
|
list allowed_ips '0.0.0.0/0'
|
||||||
|
option endpoint_host '......' # Server's public ip address
|
||||||
|
option endpoint_port '51820'
|
||||||
|
option persistent_keepalive '25'
|
||||||
|
```
|
@ -1,20 +1,11 @@
|
|||||||
### Configuration file
|
### Configuration file
|
||||||
|
|
||||||
You need to create a configuration file in INI format with your api key in `$HOME/.cloudstack.ini`
|
Algo scripts will ask you for the API detail. You need to fetch the API credentials and the endpoint from the provider control panel.
|
||||||
|
|
||||||
```
|
Example for Exoscale (European cloud provider exposing CloudStack API), visit https://portal.exoscale.com/u/<your@account>/account/profile/api to gather the required information: CloudStack api key and secret.
|
||||||
[cloudstack]
|
|
||||||
endpoint = <endpoint>
|
|
||||||
key = <your api key>
|
|
||||||
secret = <your secret>
|
|
||||||
timeout = 30
|
|
||||||
```
|
|
||||||
|
|
||||||
Example for Exoscale (European cloud provider exposing CloudStack API), visit https://portal.exoscale.com/u/<your@account>/account/profile/api to gather the required information:
|
```bash
|
||||||
```
|
export CLOUDSTACK_KEY="<your api key>"
|
||||||
[exoscale]
|
export CLOUDSTACK_SECRET="<your secret>"
|
||||||
endpoint = https://api.exoscale.com/compute
|
export CLOUDSTACK_ENDPOINT="https://api.exoscale.com/compute"
|
||||||
key = <your api key>
|
|
||||||
secret = <your secret>
|
|
||||||
timeout = 30
|
|
||||||
```
|
```
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
## API Token
|
||||||
|
|
||||||
|
Sign into the Linode Manager and go to the
|
||||||
|
[tokens management page](https://cloud.linode.com/profile/tokens).
|
||||||
|
|
||||||
|
Click `Add a Personal Access Token`. Label your new token and select *at least* the
|
||||||
|
`Linodes` read/write permission and `StackScripts` read/write permission.
|
||||||
|
Press `Submit` and make sure to copy the displayed token
|
||||||
|
as it won't be shown again.
|
@ -1,9 +1,10 @@
|
|||||||
### Configuration file
|
### Configuration file
|
||||||
|
|
||||||
Algo requires an API key from your Scaleway account to create a server.
|
Algo requires an API key from your Scaleway account to create a server.
|
||||||
The API key is generated by going to your Scaleway credentials at [https://console.scaleway.com/account/credentials](https://console.scaleway.com/account/credentials), and then selecting "Generate new token" on the right side of the box labeled "API Tokens".
|
The API key is generated by going to your Scaleway credentials at [https://console.scaleway.com/project/credentials](https://console.scaleway.com/project/credentials), and then selecting "Generate new API key" on the right side of the box labeled "API Keys".
|
||||||
|
You'll be ask for to specify a purpose for your API key before it is created. You will then be presented and "Access key" and a "Secret key".
|
||||||
|
|
||||||
Enter this token when Algo prompts you for the `auth token`.
|
Enter the "Secret key" when Algo prompts you for the `auth token`. You won't need the "Access key".
|
||||||
This information will be pass as the `algo_scaleway_token` variable when asked for in the Algo prompt.
|
This information will be pass as the `algo_scaleway_token` variable when asked for in the Algo prompt.
|
||||||
|
|
||||||
Your organization ID is also on this page: https://console.scaleway.com/account/credentials
|
Your organization ID is also on this page: https://console.scaleway.com/account/credentials
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
# RedHat/CentOS 6.x pre-installation requirements
|
|
||||||
|
|
||||||
Many people prefer RedHat or CentOS 6 (or similar variants like Amazon Linux) for to their stability and lack of systemd. Unfortunately, there are a number of dated libraries, notably Python 2.6, that prevent Algo from running without errors. This script will prepare a RedHat, CentOS, or similar VM to deploy to Algo cloud instances.
|
|
||||||
|
|
||||||
## Step 1: Prep for RH/CentOS 6.8/Amazon
|
|
||||||
|
|
||||||
```shell
|
|
||||||
yum -y update
|
|
||||||
yum -y install epel-release
|
|
||||||
```
|
|
||||||
|
|
||||||
Enable any kernel updates:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
reboot
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 2: Install Ansible and launch Algo
|
|
||||||
|
|
||||||
RedHat/CentOS 6.x uses Python 2.6 by default, which is explicitly deprecated and produces many warnings and errors, so we must install a safe, non-invasive 3.6 tool set which has to be expressly enabled (and will not survive login sessions and reboots):
|
|
||||||
|
|
||||||
- Install the Software Collections Library (to enable Python 3.6)
|
|
||||||
```shell
|
|
||||||
yum -y install centos-release-SCL
|
|
||||||
yum -y install \
|
|
||||||
openssl-devel \
|
|
||||||
libffi-devel \
|
|
||||||
automake \
|
|
||||||
gcc \
|
|
||||||
gcc-c++ \
|
|
||||||
kernel-devel \
|
|
||||||
rh-python36-python \
|
|
||||||
rh-python36-python-devel \
|
|
||||||
rh-python36-python-setuptools \
|
|
||||||
rh-python36-python-pip \
|
|
||||||
rh-python36-python-virtualenv \
|
|
||||||
rh-python36-python-crypto \
|
|
||||||
rh-python36-PyYAML \
|
|
||||||
libselinux-python \
|
|
||||||
python-crypto \
|
|
||||||
wget \
|
|
||||||
unzip \
|
|
||||||
nano
|
|
||||||
```
|
|
||||||
|
|
||||||
- 3.6 will not be used until explicitly enabled, per login session. Enable 3.6 default for this session (needs re-run between logins & reboots)
|
|
||||||
```
|
|
||||||
scl enable rh-python36 bash
|
|
||||||
```
|
|
||||||
|
|
||||||
- We're now defaulted to 3.6. Upgrade required components
|
|
||||||
```
|
|
||||||
python3 -m pip install -U pip virtualenv pycrypto setuptools
|
|
||||||
```
|
|
||||||
|
|
||||||
- Download and uzip Algo
|
|
||||||
```
|
|
||||||
wget https://github.com/trailofbits/algo/archive/master.zip
|
|
||||||
unzip master.zip
|
|
||||||
cd algo-master || echo "No Algo directory found"
|
|
||||||
```
|
|
||||||
|
|
||||||
- Set up a virtualenv and install the local Algo dependencies (must be run from algo-master)
|
|
||||||
```
|
|
||||||
python3 -m virtualenv --python="$(command -v python3)" .env
|
|
||||||
source .env/bin/activate
|
|
||||||
python3 -m pip install -U pip virtualenv
|
|
||||||
python3 -m pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
- Edit the userlist and any other settings you desire
|
|
||||||
```
|
|
||||||
nano config.cfg
|
|
||||||
```
|
|
||||||
|
|
||||||
- Now you can run the Algo installer!
|
|
||||||
```
|
|
||||||
./algo
|
|
||||||
```
|
|
||||||
|
|
||||||
## Post-install macOS
|
|
||||||
|
|
||||||
1. Copy `./configs/*mobileconfig` to your local Mac
|
|
||||||
|
|
||||||
2. Install the VPN profile on your Mac (10.10+ required)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
/usr/bin/profiles -I -F ./x.x.x.x_NAME.mobileconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
3. To remove:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
/usr/bin/profiles -D -F ./x.x.x.x_NAME.mobileconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
The VPN connection will now appear under Networks (which can be pinned to the top menu bar if preferred)
|
|
@ -1,110 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.cloudstack import (
|
|
||||||
AnsibleCloudStack,
|
|
||||||
cs_argument_spec,
|
|
||||||
cs_required_together,
|
|
||||||
)
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cloudstack_zones
|
|
||||||
short_description: List zones on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- List zones.
|
|
||||||
version_added: '0.1'
|
|
||||||
author: Julien Bachmann (@0xmilkmix)
|
|
||||||
extends_documentation_fragment: cloudstack
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: List zones
|
|
||||||
cloudstack_zones:
|
|
||||||
register: _cs_zones
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
---
|
|
||||||
zone:
|
|
||||||
description: List of zones.
|
|
||||||
returned: success
|
|
||||||
type: list
|
|
||||||
sample:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"allocationstate": "Enabled",
|
|
||||||
"dhcpprovider": "VirtualRouter",
|
|
||||||
"id": "<id>",
|
|
||||||
"localstorageenabled": true,
|
|
||||||
"name": "ch-gva-2",
|
|
||||||
"networktype": "Basic",
|
|
||||||
"securitygroupsenabled": true,
|
|
||||||
"tags": [],
|
|
||||||
"zonetoken": "token"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allocationstate": "Enabled",
|
|
||||||
"dhcpprovider": "VirtualRouter",
|
|
||||||
"id": "<id>",
|
|
||||||
"localstorageenabled": true,
|
|
||||||
"name": "ch-dk-2",
|
|
||||||
"networktype": "Basic",
|
|
||||||
"securitygroupsenabled": true,
|
|
||||||
"tags": [],
|
|
||||||
"zonetoken": "token"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allocationstate": "Enabled",
|
|
||||||
"dhcpprovider": "VirtualRouter",
|
|
||||||
"id": "<id>",
|
|
||||||
"localstorageenabled": true,
|
|
||||||
"name": "at-vie-1",
|
|
||||||
"networktype": "Basic",
|
|
||||||
"securitygroupsenabled": true,
|
|
||||||
"tags": [],
|
|
||||||
"zonetoken": "token"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allocationstate": "Enabled",
|
|
||||||
"dhcpprovider": "VirtualRouter",
|
|
||||||
"id": "<id>",
|
|
||||||
"localstorageenabled": true,
|
|
||||||
"name": "de-fra-1",
|
|
||||||
"networktype": "Basic",
|
|
||||||
"securitygroupsenabled": true,
|
|
||||||
"tags": [],
|
|
||||||
"zonetoken": "token"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
'''
|
|
||||||
|
|
||||||
class AnsibleCloudStackZones(AnsibleCloudStack):
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(AnsibleCloudStackZones, self).__init__(module)
|
|
||||||
self.zones = None
|
|
||||||
|
|
||||||
def get_zones(self):
|
|
||||||
args = {}
|
|
||||||
if not self.zones:
|
|
||||||
zones = self.query_api('listZones', **args)
|
|
||||||
if zones:
|
|
||||||
self.zones = zones
|
|
||||||
return self.zones
|
|
||||||
|
|
||||||
def main():
|
|
||||||
module = AnsibleModule(argument_spec={})
|
|
||||||
acs_zones = AnsibleCloudStackZones(module)
|
|
||||||
result = acs_zones.get_zones()
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,551 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright: Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: lightsail
|
|
||||||
short_description: Create or delete a virtual machine instance in AWS Lightsail
|
|
||||||
description:
|
|
||||||
- Creates or instances in AWS Lightsail and optionally wait for it to be 'running'.
|
|
||||||
version_added: "2.4"
|
|
||||||
author: "Nick Ball (@nickball)"
|
|
||||||
options:
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Indicate desired state of the target.
|
|
||||||
default: present
|
|
||||||
choices: ['present', 'absent', 'running', 'restarted', 'stopped']
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name of the instance
|
|
||||||
required: true
|
|
||||||
default : null
|
|
||||||
zone:
|
|
||||||
description:
|
|
||||||
- AWS availability zone in which to launch the instance. Required when state='present'
|
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
blueprint_id:
|
|
||||||
description:
|
|
||||||
- ID of the instance blueprint image. Required when state='present'
|
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
bundle_id:
|
|
||||||
description:
|
|
||||||
- Bundle of specification info for the instance. Required when state='present'
|
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
user_data:
|
|
||||||
description:
|
|
||||||
- Launch script that can configure the instance with additional data
|
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
key_pair_name:
|
|
||||||
description:
|
|
||||||
- Name of the key pair to use with the instance
|
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
wait:
|
|
||||||
description:
|
|
||||||
- Wait for the instance to be in state 'running' before returning. If wait is "no" an ip_address may not be returned
|
|
||||||
default: "yes"
|
|
||||||
choices: [ "yes", "no" ]
|
|
||||||
wait_timeout:
|
|
||||||
description:
|
|
||||||
- How long before wait gives up, in seconds.
|
|
||||||
default: 300
|
|
||||||
open_ports:
|
|
||||||
description:
|
|
||||||
- Adds public ports to an Amazon Lightsail instance.
|
|
||||||
default: null
|
|
||||||
suboptions:
|
|
||||||
from_port:
|
|
||||||
description: Begin of the range
|
|
||||||
required: true
|
|
||||||
default: null
|
|
||||||
to_port:
|
|
||||||
description: End of the range
|
|
||||||
required: true
|
|
||||||
default: null
|
|
||||||
protocol:
|
|
||||||
description: Accepted traffic protocol.
|
|
||||||
required: true
|
|
||||||
choices:
|
|
||||||
- udp
|
|
||||||
- tcp
|
|
||||||
- all
|
|
||||||
default: null
|
|
||||||
requirements:
|
|
||||||
- "python >= 2.6"
|
|
||||||
- boto3
|
|
||||||
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- aws
|
|
||||||
- ec2
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
# Create a new Lightsail instance, register the instance details
|
|
||||||
- lightsail:
|
|
||||||
state: present
|
|
||||||
name: myinstance
|
|
||||||
region: us-east-1
|
|
||||||
zone: us-east-1a
|
|
||||||
blueprint_id: ubuntu_16_04
|
|
||||||
bundle_id: nano_1_0
|
|
||||||
key_pair_name: id_rsa
|
|
||||||
user_data: " echo 'hello world' > /home/ubuntu/test.txt"
|
|
||||||
wait_timeout: 500
|
|
||||||
open_ports:
|
|
||||||
- from_port: 4500
|
|
||||||
to_port: 4500
|
|
||||||
protocol: udp
|
|
||||||
- from_port: 500
|
|
||||||
to_port: 500
|
|
||||||
protocol: udp
|
|
||||||
register: my_instance
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
msg: "Name is {{ my_instance.instance.name }}"
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
msg: "IP is {{ my_instance.instance.publicIpAddress }}"
|
|
||||||
|
|
||||||
# Delete an instance if present
|
|
||||||
- lightsail:
|
|
||||||
state: absent
|
|
||||||
region: us-east-1
|
|
||||||
name: myinstance
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
changed:
|
|
||||||
description: if a snapshot has been modified/created
|
|
||||||
returned: always
|
|
||||||
type: bool
|
|
||||||
sample:
|
|
||||||
changed: true
|
|
||||||
instance:
|
|
||||||
description: instance data
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
arn: "arn:aws:lightsail:us-east-1:448830907657:Instance/1fef0175-d6c8-480e-84fa-214f969cda87"
|
|
||||||
blueprint_id: "ubuntu_16_04"
|
|
||||||
blueprint_name: "Ubuntu"
|
|
||||||
bundle_id: "nano_1_0"
|
|
||||||
created_at: "2017-03-27T08:38:59.714000-04:00"
|
|
||||||
hardware:
|
|
||||||
cpu_count: 1
|
|
||||||
ram_size_in_gb: 0.5
|
|
||||||
is_static_ip: false
|
|
||||||
location:
|
|
||||||
availability_zone: "us-east-1a"
|
|
||||||
region_name: "us-east-1"
|
|
||||||
name: "my_instance"
|
|
||||||
networking:
|
|
||||||
monthly_transfer:
|
|
||||||
gb_per_month_allocated: 1024
|
|
||||||
ports:
|
|
||||||
- access_direction: "inbound"
|
|
||||||
access_from: "Anywhere (0.0.0.0/0)"
|
|
||||||
access_type: "public"
|
|
||||||
common_name: ""
|
|
||||||
from_port: 80
|
|
||||||
protocol: tcp
|
|
||||||
to_port: 80
|
|
||||||
- access_direction: "inbound"
|
|
||||||
access_from: "Anywhere (0.0.0.0/0)"
|
|
||||||
access_type: "public"
|
|
||||||
common_name: ""
|
|
||||||
from_port: 22
|
|
||||||
protocol: tcp
|
|
||||||
to_port: 22
|
|
||||||
private_ip_address: "172.26.8.14"
|
|
||||||
public_ip_address: "34.207.152.202"
|
|
||||||
resource_type: "Instance"
|
|
||||||
ssh_key_name: "keypair"
|
|
||||||
state:
|
|
||||||
code: 16
|
|
||||||
name: running
|
|
||||||
support_code: "588307843083/i-0997c97831ee21e33"
|
|
||||||
username: "ubuntu"
|
|
||||||
'''
|
|
||||||
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
try:
|
|
||||||
import botocore
|
|
||||||
HAS_BOTOCORE = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_BOTOCORE = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
import boto3
|
|
||||||
except ImportError:
|
|
||||||
# will be caught by imported HAS_BOTO3
|
|
||||||
pass
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.ec2 import (ec2_argument_spec, get_aws_connection_info, boto3_conn,
|
|
||||||
HAS_BOTO3, camel_dict_to_snake_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def create_instance(module, client, instance_name):
|
|
||||||
"""
|
|
||||||
Create an instance
|
|
||||||
|
|
||||||
module: Ansible module object
|
|
||||||
client: authenticated lightsail connection object
|
|
||||||
instance_name: name of instance to delete
|
|
||||||
|
|
||||||
Returns a dictionary of instance information
|
|
||||||
about the new instance.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
# Check if instance already exists
|
|
||||||
inst = None
|
|
||||||
try:
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['Error']['Code'] != 'NotFoundException':
|
|
||||||
module.fail_json(msg='Error finding instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
zone = module.params.get('zone')
|
|
||||||
blueprint_id = module.params.get('blueprint_id')
|
|
||||||
bundle_id = module.params.get('bundle_id')
|
|
||||||
user_data = module.params.get('user_data')
|
|
||||||
user_data = '' if user_data is None else user_data
|
|
||||||
|
|
||||||
wait = module.params.get('wait')
|
|
||||||
wait_timeout = int(module.params.get('wait_timeout'))
|
|
||||||
wait_max = time.time() + wait_timeout
|
|
||||||
|
|
||||||
if module.params.get('key_pair_name'):
|
|
||||||
key_pair_name = module.params.get('key_pair_name')
|
|
||||||
else:
|
|
||||||
key_pair_name = ''
|
|
||||||
|
|
||||||
if module.params.get('open_ports'):
|
|
||||||
open_ports = module.params.get('open_ports')
|
|
||||||
else:
|
|
||||||
open_ports = '[]'
|
|
||||||
|
|
||||||
resp = None
|
|
||||||
if inst is None:
|
|
||||||
try:
|
|
||||||
resp = client.create_instances(
|
|
||||||
instanceNames=[
|
|
||||||
instance_name
|
|
||||||
],
|
|
||||||
availabilityZone=zone,
|
|
||||||
blueprintId=blueprint_id,
|
|
||||||
bundleId=bundle_id,
|
|
||||||
userData=user_data,
|
|
||||||
keyPairName=key_pair_name,
|
|
||||||
)
|
|
||||||
resp = resp['operations'][0]
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
module.fail_json(msg='Unable to create instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
|
|
||||||
# Wait for instance to become running
|
|
||||||
if wait:
|
|
||||||
while (wait_max > time.time()) and (inst is not None and inst['state']['name'] != "running"):
|
|
||||||
try:
|
|
||||||
time.sleep(2)
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['ResponseMetadata']['HTTPStatusCode'] == "403":
|
|
||||||
module.fail_json(msg="Failed to start/stop instance {0}. Check that you have permissions to perform the operation".format(instance_name),
|
|
||||||
exception=traceback.format_exc())
|
|
||||||
elif e.response['Error']['Code'] == "RequestExpired":
|
|
||||||
module.fail_json(msg="RequestExpired: Failed to start instance {0}.".format(instance_name), exception=traceback.format_exc())
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# Timed out
|
|
||||||
if wait and not changed and wait_max <= time.time():
|
|
||||||
module.fail_json(msg="Wait for instance start timeout at %s" % time.asctime())
|
|
||||||
|
|
||||||
# Attempt to open ports
|
|
||||||
if open_ports:
|
|
||||||
if inst is not None:
|
|
||||||
try:
|
|
||||||
for o in open_ports:
|
|
||||||
resp = client.open_instance_public_ports(
|
|
||||||
instanceName=instance_name,
|
|
||||||
portInfo={
|
|
||||||
'fromPort': o['from_port'],
|
|
||||||
'toPort': o['to_port'],
|
|
||||||
'protocol': o['protocol']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
module.fail_json(msg='Error opening ports for instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
return (changed, inst)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_instance(module, client, instance_name):
|
|
||||||
"""
|
|
||||||
Terminates an instance
|
|
||||||
|
|
||||||
module: Ansible module object
|
|
||||||
client: authenticated lightsail connection object
|
|
||||||
instance_name: name of instance to delete
|
|
||||||
|
|
||||||
Returns a dictionary of instance information
|
|
||||||
about the instance deleted (pre-deletion).
|
|
||||||
|
|
||||||
If the instance to be deleted is running
|
|
||||||
"changed" will be set to False.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# It looks like deleting removes the instance immediately, nothing to wait for
|
|
||||||
wait = module.params.get('wait')
|
|
||||||
wait_timeout = int(module.params.get('wait_timeout'))
|
|
||||||
wait_max = time.time() + wait_timeout
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
inst = None
|
|
||||||
try:
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['Error']['Code'] != 'NotFoundException':
|
|
||||||
module.fail_json(msg='Error finding instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
# Wait for instance to exit transition state before deleting
|
|
||||||
if wait:
|
|
||||||
while wait_max > time.time() and inst is not None and inst['state']['name'] in ('pending', 'stopping'):
|
|
||||||
try:
|
|
||||||
time.sleep(5)
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['ResponseMetadata']['HTTPStatusCode'] == "403":
|
|
||||||
module.fail_json(msg="Failed to delete instance {0}. Check that you have permissions to perform the operation.".format(instance_name),
|
|
||||||
exception=traceback.format_exc())
|
|
||||||
elif e.response['Error']['Code'] == "RequestExpired":
|
|
||||||
module.fail_json(msg="RequestExpired: Failed to delete instance {0}.".format(instance_name), exception=traceback.format_exc())
|
|
||||||
# sleep and retry
|
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
# Attempt to delete
|
|
||||||
if inst is not None:
|
|
||||||
while not changed and ((wait and wait_max > time.time()) or (not wait)):
|
|
||||||
try:
|
|
||||||
client.delete_instance(instanceName=instance_name)
|
|
||||||
changed = True
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
module.fail_json(msg='Error deleting instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
# Timed out
|
|
||||||
if wait and not changed and wait_max <= time.time():
|
|
||||||
module.fail_json(msg="wait for instance delete timeout at %s" % time.asctime())
|
|
||||||
|
|
||||||
return (changed, inst)
|
|
||||||
|
|
||||||
|
|
||||||
def restart_instance(module, client, instance_name):
|
|
||||||
"""
|
|
||||||
Reboot an existing instance
|
|
||||||
|
|
||||||
module: Ansible module object
|
|
||||||
client: authenticated lightsail connection object
|
|
||||||
instance_name: name of instance to reboot
|
|
||||||
|
|
||||||
Returns a dictionary of instance information
|
|
||||||
about the restarted instance
|
|
||||||
|
|
||||||
If the instance was not able to reboot,
|
|
||||||
"changed" will be set to False.
|
|
||||||
|
|
||||||
Wait will not apply here as this is an OS-level operation
|
|
||||||
"""
|
|
||||||
wait = module.params.get('wait')
|
|
||||||
wait_timeout = int(module.params.get('wait_timeout'))
|
|
||||||
wait_max = time.time() + wait_timeout
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
inst = None
|
|
||||||
try:
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['Error']['Code'] != 'NotFoundException':
|
|
||||||
module.fail_json(msg='Error finding instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
# Wait for instance to exit transition state before state change
|
|
||||||
if wait:
|
|
||||||
while wait_max > time.time() and inst is not None and inst['state']['name'] in ('pending', 'stopping'):
|
|
||||||
try:
|
|
||||||
time.sleep(5)
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['ResponseMetadata']['HTTPStatusCode'] == "403":
|
|
||||||
module.fail_json(msg="Failed to restart instance {0}. Check that you have permissions to perform the operation.".format(instance_name),
|
|
||||||
exception=traceback.format_exc())
|
|
||||||
elif e.response['Error']['Code'] == "RequestExpired":
|
|
||||||
module.fail_json(msg="RequestExpired: Failed to restart instance {0}.".format(instance_name), exception=traceback.format_exc())
|
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
# send reboot
|
|
||||||
if inst is not None:
|
|
||||||
try:
|
|
||||||
client.reboot_instance(instanceName=instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['Error']['Code'] != 'NotFoundException':
|
|
||||||
module.fail_json(msg='Unable to reboot instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
return (changed, inst)
|
|
||||||
|
|
||||||
|
|
||||||
def startstop_instance(module, client, instance_name, state):
|
|
||||||
"""
|
|
||||||
Starts or stops an existing instance
|
|
||||||
|
|
||||||
module: Ansible module object
|
|
||||||
client: authenticated lightsail connection object
|
|
||||||
instance_name: name of instance to start/stop
|
|
||||||
state: Target state ("running" or "stopped")
|
|
||||||
|
|
||||||
Returns a dictionary of instance information
|
|
||||||
about the instance started/stopped
|
|
||||||
|
|
||||||
If the instance was not able to state change,
|
|
||||||
"changed" will be set to False.
|
|
||||||
|
|
||||||
"""
|
|
||||||
wait = module.params.get('wait')
|
|
||||||
wait_timeout = int(module.params.get('wait_timeout'))
|
|
||||||
wait_max = time.time() + wait_timeout
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
inst = None
|
|
||||||
try:
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['Error']['Code'] != 'NotFoundException':
|
|
||||||
module.fail_json(msg='Error finding instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
|
|
||||||
# Wait for instance to exit transition state before state change
|
|
||||||
if wait:
|
|
||||||
while wait_max > time.time() and inst is not None and inst['state']['name'] in ('pending', 'stopping'):
|
|
||||||
try:
|
|
||||||
time.sleep(5)
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
if e.response['ResponseMetadata']['HTTPStatusCode'] == "403":
|
|
||||||
module.fail_json(msg="Failed to start/stop instance {0}. Check that you have permissions to perform the operation".format(instance_name),
|
|
||||||
exception=traceback.format_exc())
|
|
||||||
elif e.response['Error']['Code'] == "RequestExpired":
|
|
||||||
module.fail_json(msg="RequestExpired: Failed to start/stop instance {0}.".format(instance_name), exception=traceback.format_exc())
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# Try state change
|
|
||||||
if inst is not None and inst['state']['name'] != state:
|
|
||||||
try:
|
|
||||||
if state == 'running':
|
|
||||||
client.start_instance(instanceName=instance_name)
|
|
||||||
else:
|
|
||||||
client.stop_instance(instanceName=instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
module.fail_json(msg='Unable to change state for instance {0}, error: {1}'.format(instance_name, e))
|
|
||||||
changed = True
|
|
||||||
# Grab current instance info
|
|
||||||
inst = _find_instance_info(client, instance_name)
|
|
||||||
|
|
||||||
return (changed, inst)
|
|
||||||
|
|
||||||
|
|
||||||
def core(module):
|
|
||||||
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
|
|
||||||
if not region:
|
|
||||||
module.fail_json(msg='region must be specified')
|
|
||||||
|
|
||||||
client = None
|
|
||||||
try:
|
|
||||||
client = boto3_conn(module, conn_type='client', resource='lightsail',
|
|
||||||
region=region, endpoint=ec2_url, **aws_connect_kwargs)
|
|
||||||
except (botocore.exceptions.ClientError, botocore.exceptions.ValidationError) as e:
|
|
||||||
module.fail_json(msg='Failed while connecting to the lightsail service: %s' % e, exception=traceback.format_exc())
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
state = module.params['state']
|
|
||||||
name = module.params['name']
|
|
||||||
|
|
||||||
if state == 'absent':
|
|
||||||
changed, instance_dict = delete_instance(module, client, name)
|
|
||||||
elif state in ('running', 'stopped'):
|
|
||||||
changed, instance_dict = startstop_instance(module, client, name, state)
|
|
||||||
elif state == 'restarted':
|
|
||||||
changed, instance_dict = restart_instance(module, client, name)
|
|
||||||
elif state == 'present':
|
|
||||||
changed, instance_dict = create_instance(module, client, name)
|
|
||||||
|
|
||||||
module.exit_json(changed=changed, instance=camel_dict_to_snake_dict(instance_dict))
|
|
||||||
|
|
||||||
|
|
||||||
def _find_instance_info(client, instance_name):
|
|
||||||
''' handle exceptions where this function is called '''
|
|
||||||
inst = None
|
|
||||||
try:
|
|
||||||
inst = client.get_instance(instanceName=instance_name)
|
|
||||||
except botocore.exceptions.ClientError as e:
|
|
||||||
raise
|
|
||||||
return inst['instance']
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argument_spec = ec2_argument_spec()
|
|
||||||
argument_spec.update(dict(
|
|
||||||
name=dict(type='str', required=True),
|
|
||||||
state=dict(type='str', default='present', choices=['present', 'absent', 'stopped', 'running', 'restarted']),
|
|
||||||
zone=dict(type='str'),
|
|
||||||
blueprint_id=dict(type='str'),
|
|
||||||
bundle_id=dict(type='str'),
|
|
||||||
key_pair_name=dict(type='str'),
|
|
||||||
user_data=dict(type='str'),
|
|
||||||
wait=dict(type='bool', default=True),
|
|
||||||
wait_timeout=dict(default=300),
|
|
||||||
open_ports=dict(type='list')
|
|
||||||
))
|
|
||||||
|
|
||||||
module = AnsibleModule(argument_spec=argument_spec)
|
|
||||||
|
|
||||||
if not HAS_BOTO3:
|
|
||||||
module.fail_json(msg='Python module "boto3" is missing, please install it')
|
|
||||||
|
|
||||||
if not HAS_BOTOCORE:
|
|
||||||
module.fail_json(msg='Python module "botocore" is missing, please install it')
|
|
||||||
|
|
||||||
try:
|
|
||||||
core(module)
|
|
||||||
except (botocore.exceptions.ClientError, Exception) as e:
|
|
||||||
module.fail_json(msg=str(e), exception=traceback.format_exc())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib
|
||||||
|
from ansible.module_utils.linode import get_user_agent
|
||||||
|
|
||||||
|
LINODE_IMP_ERR = None
|
||||||
|
try:
|
||||||
|
from linode_api4 import StackScript, LinodeClient
|
||||||
|
HAS_LINODE_DEPENDENCY = True
|
||||||
|
except ImportError:
|
||||||
|
LINODE_IMP_ERR = traceback.format_exc()
|
||||||
|
HAS_LINODE_DEPENDENCY = False
|
||||||
|
|
||||||
|
|
||||||
|
def create_stackscript(module, client, **kwargs):
|
||||||
|
"""Creates a stackscript and handles return format."""
|
||||||
|
try:
|
||||||
|
response = client.linode.stackscript_create(**kwargs)
|
||||||
|
return response._raw_json
|
||||||
|
except Exception as exception:
|
||||||
|
module.fail_json(msg='Unable to query the Linode API. Saw: %s' % exception)
|
||||||
|
|
||||||
|
|
||||||
|
def stackscript_available(module, client):
|
||||||
|
"""Try to retrieve a stackscript."""
|
||||||
|
try:
|
||||||
|
label = module.params['label']
|
||||||
|
desc = module.params['description']
|
||||||
|
|
||||||
|
result = client.linode.stackscripts(StackScript.label == label,
|
||||||
|
StackScript.description == desc,
|
||||||
|
mine_only=True
|
||||||
|
)
|
||||||
|
return result[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
except Exception as exception:
|
||||||
|
module.fail_json(msg='Unable to query the Linode API. Saw: %s' % exception)
|
||||||
|
|
||||||
|
|
||||||
|
def initialise_module():
|
||||||
|
"""Initialise the module parameter specification."""
|
||||||
|
return AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
label=dict(type='str', required=True),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
choices=['present', 'absent']
|
||||||
|
),
|
||||||
|
access_token=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
no_log=True,
|
||||||
|
fallback=(env_fallback, ['LINODE_ACCESS_TOKEN']),
|
||||||
|
),
|
||||||
|
script=dict(type='str', required=True),
|
||||||
|
images=dict(type='list', required=True),
|
||||||
|
description=dict(type='str', required=False),
|
||||||
|
public=dict(type='bool', required=False, default=False),
|
||||||
|
),
|
||||||
|
supports_check_mode=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_client(module):
|
||||||
|
"""Build a LinodeClient."""
|
||||||
|
return LinodeClient(
|
||||||
|
module.params['access_token'],
|
||||||
|
user_agent=get_user_agent('linode_v4_module')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Module entrypoint."""
|
||||||
|
module = initialise_module()
|
||||||
|
|
||||||
|
if not HAS_LINODE_DEPENDENCY:
|
||||||
|
module.fail_json(msg=missing_required_lib('linode-api4'), exception=LINODE_IMP_ERR)
|
||||||
|
|
||||||
|
client = build_client(module)
|
||||||
|
stackscript = stackscript_available(module, client)
|
||||||
|
|
||||||
|
if module.params['state'] == 'present' and stackscript is not None:
|
||||||
|
module.exit_json(changed=False, stackscript=stackscript._raw_json)
|
||||||
|
|
||||||
|
elif module.params['state'] == 'present' and stackscript is None:
|
||||||
|
stackscript_json = create_stackscript(
|
||||||
|
module, client,
|
||||||
|
label=module.params['label'],
|
||||||
|
script=module.params['script'],
|
||||||
|
images=module.params['images'],
|
||||||
|
desc=module.params['description'],
|
||||||
|
public=module.params['public'],
|
||||||
|
)
|
||||||
|
module.exit_json(changed=True, stackscript=stackscript_json)
|
||||||
|
|
||||||
|
elif module.params['state'] == 'absent' and stackscript is not None:
|
||||||
|
stackscript.delete()
|
||||||
|
module.exit_json(changed=True, stackscript=stackscript._raw_json)
|
||||||
|
|
||||||
|
elif module.params['state'] == 'absent' and stackscript is None:
|
||||||
|
module.exit_json(changed=False, stackscript={})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib
|
||||||
|
from ansible.module_utils.linode import get_user_agent
|
||||||
|
|
||||||
|
LINODE_IMP_ERR = None
|
||||||
|
try:
|
||||||
|
from linode_api4 import Instance, LinodeClient
|
||||||
|
HAS_LINODE_DEPENDENCY = True
|
||||||
|
except ImportError:
|
||||||
|
LINODE_IMP_ERR = traceback.format_exc()
|
||||||
|
HAS_LINODE_DEPENDENCY = False
|
||||||
|
|
||||||
|
|
||||||
|
def create_linode(module, client, **kwargs):
|
||||||
|
"""Creates a Linode instance and handles return format."""
|
||||||
|
if kwargs['root_pass'] is None:
|
||||||
|
kwargs.pop('root_pass')
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = client.linode.instance_create(**kwargs)
|
||||||
|
except Exception as exception:
|
||||||
|
module.fail_json(msg='Unable to query the Linode API. Saw: %s' % exception)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(response, tuple):
|
||||||
|
instance, root_pass = response
|
||||||
|
instance_json = instance._raw_json
|
||||||
|
instance_json.update({'root_pass': root_pass})
|
||||||
|
return instance_json
|
||||||
|
else:
|
||||||
|
return response._raw_json
|
||||||
|
except TypeError:
|
||||||
|
module.fail_json(msg='Unable to parse Linode instance creation'
|
||||||
|
' response. Please raise a bug against this'
|
||||||
|
' module on https://github.com/ansible/ansible/issues'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_instance_from_label(module, client):
|
||||||
|
"""Try to retrieve an instance based on a label."""
|
||||||
|
try:
|
||||||
|
label = module.params['label']
|
||||||
|
result = client.linode.instances(Instance.label == label)
|
||||||
|
return result[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
except Exception as exception:
|
||||||
|
module.fail_json(msg='Unable to query the Linode API. Saw: %s' % exception)
|
||||||
|
|
||||||
|
|
||||||
|
def initialise_module():
|
||||||
|
"""Initialise the module parameter specification."""
|
||||||
|
return AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
label=dict(type='str', required=True),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
choices=['present', 'absent']
|
||||||
|
),
|
||||||
|
access_token=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
no_log=True,
|
||||||
|
fallback=(env_fallback, ['LINODE_ACCESS_TOKEN']),
|
||||||
|
),
|
||||||
|
authorized_keys=dict(type='list', required=False),
|
||||||
|
group=dict(type='str', required=False),
|
||||||
|
image=dict(type='str', required=False),
|
||||||
|
region=dict(type='str', required=False),
|
||||||
|
root_pass=dict(type='str', required=False, no_log=True),
|
||||||
|
tags=dict(type='list', required=False),
|
||||||
|
type=dict(type='str', required=False),
|
||||||
|
stackscript_id=dict(type='int', required=False),
|
||||||
|
),
|
||||||
|
supports_check_mode=False,
|
||||||
|
required_one_of=(
|
||||||
|
['state', 'label'],
|
||||||
|
),
|
||||||
|
required_together=(
|
||||||
|
['region', 'image', 'type'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_client(module):
|
||||||
|
"""Build a LinodeClient."""
|
||||||
|
return LinodeClient(
|
||||||
|
module.params['access_token'],
|
||||||
|
user_agent=get_user_agent('linode_v4_module')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Module entrypoint."""
|
||||||
|
module = initialise_module()
|
||||||
|
|
||||||
|
if not HAS_LINODE_DEPENDENCY:
|
||||||
|
module.fail_json(msg=missing_required_lib('linode-api4'), exception=LINODE_IMP_ERR)
|
||||||
|
|
||||||
|
client = build_client(module)
|
||||||
|
instance = maybe_instance_from_label(module, client)
|
||||||
|
|
||||||
|
if module.params['state'] == 'present' and instance is not None:
|
||||||
|
module.exit_json(changed=False, instance=instance._raw_json)
|
||||||
|
|
||||||
|
elif module.params['state'] == 'present' and instance is None:
|
||||||
|
instance_json = create_linode(
|
||||||
|
module, client,
|
||||||
|
authorized_keys=module.params['authorized_keys'],
|
||||||
|
group=module.params['group'],
|
||||||
|
image=module.params['image'],
|
||||||
|
label=module.params['label'],
|
||||||
|
region=module.params['region'],
|
||||||
|
root_pass=module.params['root_pass'],
|
||||||
|
tags=module.params['tags'],
|
||||||
|
ltype=module.params['type'],
|
||||||
|
stackscript_id=module.params['stackscript_id'],
|
||||||
|
)
|
||||||
|
module.exit_json(changed=True, instance=instance_json)
|
||||||
|
|
||||||
|
elif module.params['state'] == 'absent' and instance is not None:
|
||||||
|
instance.delete()
|
||||||
|
module.exit_json(changed=True, instance=instance._raw_json)
|
||||||
|
|
||||||
|
elif module.params['state'] == 'absent' and instance is None:
|
||||||
|
module.exit_json(changed=False, instance={})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,54 +1,53 @@
|
|||||||
---
|
---
|
||||||
- block:
|
- block:
|
||||||
- name: Display the invocation environment
|
- name: Display the invocation environment
|
||||||
shell: >
|
shell: >
|
||||||
./algo-showenv.sh \
|
./algo-showenv.sh \
|
||||||
'algo_provider "{{ algo_provider }}"' \
|
'algo_provider "{{ algo_provider }}"' \
|
||||||
{% if ipsec_enabled %}
|
{% if ipsec_enabled %}
|
||||||
'algo_ondemand_cellular "{{ algo_ondemand_cellular }}"' \
|
'algo_ondemand_cellular "{{ algo_ondemand_cellular }}"' \
|
||||||
'algo_ondemand_wifi "{{ algo_ondemand_wifi }}"' \
|
'algo_ondemand_wifi "{{ algo_ondemand_wifi }}"' \
|
||||||
'algo_ondemand_wifi_exclude "{{ algo_ondemand_wifi_exclude }}"' \
|
'algo_ondemand_wifi_exclude "{{ algo_ondemand_wifi_exclude }}"' \
|
||||||
{% endif %}
|
{% endif %}
|
||||||
'algo_dns_adblocking "{{ algo_dns_adblocking }}"' \
|
'algo_dns_adblocking "{{ algo_dns_adblocking }}"' \
|
||||||
'algo_ssh_tunneling "{{ algo_ssh_tunneling }}"' \
|
'algo_ssh_tunneling "{{ algo_ssh_tunneling }}"' \
|
||||||
'wireguard_enabled "{{ wireguard_enabled }}"' \
|
'wireguard_enabled "{{ wireguard_enabled }}"' \
|
||||||
'dns_encryption "{{ dns_encryption }}"' \
|
'dns_encryption "{{ dns_encryption }}"' \
|
||||||
> /dev/tty
|
> /dev/tty || true
|
||||||
tags: debug
|
tags: debug
|
||||||
|
|
||||||
- name: Install the requirements
|
- name: Install the requirements
|
||||||
pip:
|
pip:
|
||||||
state: latest
|
state: present
|
||||||
name:
|
name:
|
||||||
- pyOpenSSL
|
- pyOpenSSL>=0.15
|
||||||
- jinja2==2.8
|
- segno
|
||||||
- segno
|
tags:
|
||||||
tags:
|
- always
|
||||||
- always
|
- skip_ansible_lint
|
||||||
- skip_ansible_lint
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: Generate the SSH private key
|
- name: Generate the SSH private key
|
||||||
openssl_privatekey:
|
openssl_privatekey:
|
||||||
path: "{{ SSH_keys.private }}"
|
path: "{{ SSH_keys.private }}"
|
||||||
size: 2048
|
size: 4096
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
type: RSA
|
type: RSA
|
||||||
|
|
||||||
- name: Generate the SSH public key
|
- name: Generate the SSH public key
|
||||||
openssl_publickey:
|
openssl_publickey:
|
||||||
path: "{{ SSH_keys.public }}"
|
path: "{{ SSH_keys.public }}"
|
||||||
privatekey_path: "{{ SSH_keys.private }}"
|
privatekey_path: "{{ SSH_keys.private }}"
|
||||||
format: OpenSSH
|
format: OpenSSH
|
||||||
|
|
||||||
- name: Copy the private SSH key to /tmp
|
- name: Copy the private SSH key to /tmp
|
||||||
copy:
|
copy:
|
||||||
src: "{{ SSH_keys.private }}"
|
src: "{{ SSH_keys.private }}"
|
||||||
dest: "{{ SSH_keys.private_tmp }}"
|
dest: "{{ SSH_keys.private_tmp }}"
|
||||||
force: true
|
force: true
|
||||||
mode: '0600'
|
mode: "0600"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
when: algo_provider != "local"
|
when: algo_provider != "local"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Linux | set OS specific facts
|
- name: Linux | set OS specific facts
|
||||||
set_fact:
|
set_fact:
|
||||||
tmpfs_volume_name: "AlgoVPN-{{ IP_subject_alt_name }}"
|
tmpfs_volume_name: AlgoVPN-{{ IP_subject_alt_name }}
|
||||||
tmpfs_volume_path: /dev/shm
|
tmpfs_volume_path: /dev/shm
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
---
|
---
|
||||||
- name: Linux | Delete the PKI directory
|
- name: Linux | Delete the PKI directory
|
||||||
file:
|
file:
|
||||||
path: "/{{ facts.tmpfs_volume_path }}/{{ facts.tmpfs_volume_name }}/"
|
path: /{{ facts.tmpfs_volume_path }}/{{ facts.tmpfs_volume_name }}/
|
||||||
state: absent
|
state: absent
|
||||||
when: facts.ansible_system == "Linux"
|
when: facts.ansible_system == "Linux"
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: MacOS | check fs the ramdisk exists
|
- name: MacOS | check fs the ramdisk exists
|
||||||
command: /usr/sbin/diskutil info "{{ facts.tmpfs_volume_name }}"
|
command: /usr/sbin/diskutil info "{{ facts.tmpfs_volume_name }}"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
register: diskutil_info
|
register: diskutil_info
|
||||||
|
|
||||||
- name: MacOS | unmount and eject the ram disk
|
- name: MacOS | unmount and eject the ram disk
|
||||||
shell: >
|
shell: >
|
||||||
/usr/sbin/diskutil umount force "/{{ facts.tmpfs_volume_path }}/{{ facts.tmpfs_volume_name }}/" &&
|
/usr/sbin/diskutil umount force "/{{ facts.tmpfs_volume_path }}/{{ facts.tmpfs_volume_name }}/" &&
|
||||||
/usr/sbin/diskutil eject "{{ facts.tmpfs_volume_name }}"
|
/usr/sbin/diskutil eject "{{ facts.tmpfs_volume_name }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
when: diskutil_info.rc == 0
|
when: diskutil_info.rc == 0
|
||||||
register: result
|
register: result
|
||||||
until: result.rc == 0
|
until: result.rc == 0
|
||||||
retries: 5
|
retries: 5
|
||||||
delay: 3
|
delay: 3
|
||||||
when:
|
when:
|
||||||
- facts.ansible_system == "Darwin"
|
- facts.ansible_system == "Darwin"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
ansible==2.9.22
|
ansible==9.1.0
|
||||||
netaddr
|
jinja2~=3.0.3
|
||||||
|
netaddr
|
@ -1,3 +1,3 @@
|
|||||||
---
|
---
|
||||||
- name: restart strongswan
|
- name: restart strongswan
|
||||||
service: name=strongswan state=restarted
|
service: name={{ strongswan_service }} state=restarted
|
||||||
|
@ -1,42 +1,6 @@
|
|||||||
---
|
---
|
||||||
- name: Install requirements
|
- name: Install requirements
|
||||||
pip:
|
pip:
|
||||||
name:
|
requirements: https://raw.githubusercontent.com/ansible-collections/azure/v1.13.0/requirements-azure.txt
|
||||||
- packaging
|
|
||||||
- requests[security]
|
|
||||||
- azure-cli-core==2.0.35
|
|
||||||
- azure-cli-nspkg==3.0.2
|
|
||||||
- azure-common==1.1.11
|
|
||||||
- azure-mgmt-authorization==0.51.1
|
|
||||||
- azure-mgmt-batch==5.0.1
|
|
||||||
- azure-mgmt-cdn==3.0.0
|
|
||||||
- azure-mgmt-compute==4.4.0
|
|
||||||
- azure-mgmt-containerinstance==1.4.0
|
|
||||||
- azure-mgmt-containerregistry==2.0.0
|
|
||||||
- azure-mgmt-containerservice==4.4.0
|
|
||||||
- azure-mgmt-dns==2.1.0
|
|
||||||
- azure-mgmt-keyvault==1.1.0
|
|
||||||
- azure-mgmt-marketplaceordering==0.1.0
|
|
||||||
- azure-mgmt-monitor==0.5.2
|
|
||||||
- azure-mgmt-network==2.3.0
|
|
||||||
- azure-mgmt-nspkg==2.0.0
|
|
||||||
- azure-mgmt-redis==5.0.0
|
|
||||||
- azure-mgmt-resource==2.1.0
|
|
||||||
- azure-mgmt-rdbms==1.4.1
|
|
||||||
- azure-mgmt-servicebus==0.5.3
|
|
||||||
- azure-mgmt-sql==0.10.0
|
|
||||||
- azure-mgmt-storage==3.1.0
|
|
||||||
- azure-mgmt-trafficmanager==0.50.0
|
|
||||||
- azure-mgmt-web==0.41.0
|
|
||||||
- azure-nspkg==2.0.0
|
|
||||||
- azure-storage==0.35.1
|
|
||||||
- msrest==0.6.1
|
|
||||||
- msrestazure==0.5.0
|
|
||||||
- azure-keyvault==1.0.0a1
|
|
||||||
- azure-graphrbac==0.40.0
|
|
||||||
- azure-mgmt-cosmosdb==0.5.2
|
|
||||||
- azure-mgmt-hdinsight==0.1.0
|
|
||||||
- azure-mgmt-devtestlabs==3.0.0
|
|
||||||
- azure-mgmt-loganalytics==0.2.0
|
|
||||||
state: latest
|
state: latest
|
||||||
virtualenv_python: python3
|
virtualenv_python: python3
|
||||||
|
@ -1,54 +1,65 @@
|
|||||||
---
|
---
|
||||||
- block:
|
- block:
|
||||||
- pause:
|
- pause:
|
||||||
prompt: |
|
prompt: |
|
||||||
Enter path for cloudstack.ini file (https://trailofbits.github.io/algo/cloud-cloudstack.html)
|
Enter the API key (https://trailofbits.github.io/algo/cloud-cloudstack.html):
|
||||||
[~/.cloudstack.ini]
|
echo: false
|
||||||
register: _cs_config
|
register: _cs_key
|
||||||
when:
|
when:
|
||||||
- cs_config is undefined
|
- cs_key is undefined
|
||||||
- lookup('env', 'CLOUDSTACK_CONFIG') | length <= 0
|
- lookup('env','CLOUDSTACK_KEY')|length <= 0
|
||||||
|
|
||||||
- pause:
|
- pause:
|
||||||
prompt: |
|
prompt: |
|
||||||
Specify region to use in cloudstack.ini file
|
Enter the API ssecret (https://trailofbits.github.io/algo/cloud-cloudstack.html):
|
||||||
[exoscale]
|
echo: false
|
||||||
register: _cs_region
|
register: _cs_secret
|
||||||
when:
|
when:
|
||||||
- cs_region is undefined
|
- cs_secret is undefined
|
||||||
- lookup('env', 'CLOUDSTACK_REGION') | length <= 0
|
- lookup('env','CLOUDSTACK_SECRET')|length <= 0
|
||||||
|
|
||||||
- set_fact:
|
- pause:
|
||||||
algo_cs_config: "{{ cs_config | default(_cs_config.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_CONFIG'), true) | default('~/.cloudstack.ini', true) }}"
|
prompt: |
|
||||||
algo_cs_region: "{{ cs_region | default(_cs_region.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_REGION'), true) | default('exoscale', true) }}"
|
Enter the API endpoint (https://trailofbits.github.io/algo/cloud-cloudstack.html)
|
||||||
|
[https://api.exoscale.com/compute]
|
||||||
- name: Get zones on cloud
|
register: _cs_url
|
||||||
cloudstack_zones:
|
when:
|
||||||
register: _cs_zones
|
- cs_url is undefined
|
||||||
environment:
|
- lookup('env', 'CLOUDSTACK_ENDPOINT') | length <= 0
|
||||||
CLOUDSTACK_CONFIG: "{{ algo_cs_config }}"
|
|
||||||
CLOUDSTACK_REGION: "{{ algo_cs_region }}"
|
- set_fact:
|
||||||
|
algo_cs_key: "{{ cs_key | default(_cs_key.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_KEY'), true) }}"
|
||||||
- name: Extract zones from output
|
algo_cs_token: "{{ cs_secret | default(_cs_secret.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_SECRET'), true) }}"
|
||||||
set_fact:
|
algo_cs_url: "{{ cs_url | default(_cs_url.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_ENDPOINT'), true) | default('https://api.exoscale.com/compute',\
|
||||||
cs_zones: "{{ _cs_zones['zone'] | sort(attribute='name') }}"
|
\ true) }}"
|
||||||
|
|
||||||
- name: Set the default zone
|
- name: Get zones on cloud
|
||||||
set_fact:
|
cs_zone_info:
|
||||||
default_zone: >-
|
register: _cs_zones
|
||||||
{% for z in cs_zones %}
|
environment:
|
||||||
{%- if z['name'] == "ch-gva-2" %}{{ loop.index }}{% endif %}
|
CLOUDSTACK_KEY: "{{ algo_cs_key }}"
|
||||||
{%- endfor %}
|
CLOUDSTACK_SECRET: "{{ algo_cs_token }}"
|
||||||
|
CLOUDSTACK_ENDPOINT: "{{ algo_cs_url }}"
|
||||||
- pause:
|
|
||||||
prompt: |
|
- name: Extract zones from output
|
||||||
What zone should the server be located in?
|
set_fact:
|
||||||
|
cs_zones: "{{ _cs_zones['zones'] | sort(attribute='name') }}"
|
||||||
|
|
||||||
|
- name: Set the default zone
|
||||||
|
set_fact:
|
||||||
|
default_zone: >-
|
||||||
{% for z in cs_zones %}
|
{% for z in cs_zones %}
|
||||||
{{ loop.index }}. {{ z['name'] }}
|
{%- if z['name'] == "ch-gva-2" %}{{ loop.index }}{% endif %}
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
|
|
||||||
Enter the number of your desired zone
|
- pause:
|
||||||
[{{ default_zone }}]
|
prompt: |
|
||||||
register: _algo_region
|
What zone should the server be located in?
|
||||||
when: region is undefined
|
{% for z in cs_zones %}
|
||||||
|
{{ loop.index }}. {{ z['name'] }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
Enter the number of your desired zone
|
||||||
|
[{{ default_zone }}]
|
||||||
|
register: _algo_region
|
||||||
|
when: region is undefined
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
AWSTemplateFormatVersion: '2010-09-09'
|
||||||
|
Description: 'Algo VPN stack (LightSail)'
|
||||||
|
Parameters:
|
||||||
|
InstanceTypeParameter:
|
||||||
|
Type: String
|
||||||
|
Default: 'nano_2_0'
|
||||||
|
ImageIdParameter:
|
||||||
|
Type: String
|
||||||
|
Default: 'ubuntu_20_04'
|
||||||
|
WireGuardPort:
|
||||||
|
Type: String
|
||||||
|
Default: '51820'
|
||||||
|
SshPort:
|
||||||
|
Type: String
|
||||||
|
Default: '4160'
|
||||||
|
UserData:
|
||||||
|
Type: String
|
||||||
|
Default: 'true'
|
||||||
|
Resources:
|
||||||
|
Instance:
|
||||||
|
Type: AWS::Lightsail::Instance
|
||||||
|
Properties:
|
||||||
|
BlueprintId:
|
||||||
|
Ref: ImageIdParameter
|
||||||
|
BundleId:
|
||||||
|
Ref: InstanceTypeParameter
|
||||||
|
InstanceName: !Ref AWS::StackName
|
||||||
|
Networking:
|
||||||
|
Ports:
|
||||||
|
- AccessDirection: inbound
|
||||||
|
Cidrs: ['0.0.0.0/0']
|
||||||
|
Ipv6Cidrs: ['::/0']
|
||||||
|
CommonName: SSH
|
||||||
|
FromPort: !Ref SshPort
|
||||||
|
ToPort: !Ref SshPort
|
||||||
|
Protocol: tcp
|
||||||
|
- AccessDirection: inbound
|
||||||
|
Cidrs: ['0.0.0.0/0']
|
||||||
|
Ipv6Cidrs: ['::/0']
|
||||||
|
CommonName: WireGuard
|
||||||
|
FromPort: !Ref WireGuardPort
|
||||||
|
ToPort: !Ref WireGuardPort
|
||||||
|
Protocol: udp
|
||||||
|
- AccessDirection: inbound
|
||||||
|
Cidrs: ['0.0.0.0/0']
|
||||||
|
Ipv6Cidrs: ['::/0']
|
||||||
|
CommonName: IPSec-4500
|
||||||
|
FromPort: 4500
|
||||||
|
ToPort: 4500
|
||||||
|
Protocol: udp
|
||||||
|
- AccessDirection: inbound
|
||||||
|
Cidrs: ['0.0.0.0/0']
|
||||||
|
Ipv6Cidrs: ['::/0']
|
||||||
|
CommonName: IPSec-500
|
||||||
|
FromPort: 500
|
||||||
|
ToPort: 500
|
||||||
|
Protocol: udp
|
||||||
|
Tags:
|
||||||
|
- Key: Name
|
||||||
|
Value: !Ref AWS::StackName
|
||||||
|
UserData: !Ref UserData
|
||||||
|
|
||||||
|
StaticIP:
|
||||||
|
Type: AWS::Lightsail::StaticIp
|
||||||
|
Properties:
|
||||||
|
AttachedTo: !Ref Instance
|
||||||
|
StaticIpName: !Join [ "-", [ !Ref AWS::StackName, "ip" ] ]
|
||||||
|
DependsOn:
|
||||||
|
- Instance
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
IpAddress:
|
||||||
|
Value: !GetAtt [StaticIP, IpAddress]
|
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
- name: Deploy the template
|
||||||
|
cloudformation:
|
||||||
|
aws_access_key: "{{ access_key }}"
|
||||||
|
aws_secret_key: "{{ secret_key }}"
|
||||||
|
stack_name: "{{ stack_name }}"
|
||||||
|
state: present
|
||||||
|
region: "{{ algo_region }}"
|
||||||
|
template: roles/cloud-lightsail/files/stack.yaml
|
||||||
|
template_parameters:
|
||||||
|
InstanceTypeParameter: "{{ cloud_providers.lightsail.size }}"
|
||||||
|
ImageIdParameter: "{{ cloud_providers.lightsail.image }}"
|
||||||
|
WireGuardPort: "{{ wireguard_port }}"
|
||||||
|
SshPort: "{{ ssh_port }}"
|
||||||
|
UserData: "{{ lookup('template', 'files/cloud-init/base.sh') }}"
|
||||||
|
tags:
|
||||||
|
Environment: Algo
|
||||||
|
Lightsail: true
|
||||||
|
register: stack
|
@ -0,0 +1,2 @@
|
|||||||
|
---
|
||||||
|
linode_venv: "{{ playbook_dir }}/configs/.venvs/linode"
|
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
- name: Build python virtual environment
|
||||||
|
import_tasks: venv.yml
|
||||||
|
|
||||||
|
- name: Include prompts
|
||||||
|
import_tasks: prompts.yml
|
||||||
|
|
||||||
|
- name: Set facts
|
||||||
|
set_fact:
|
||||||
|
stackscript: |
|
||||||
|
{{ lookup('template', 'files/cloud-init/base.sh') }}
|
||||||
|
mkdir -p /var/lib/cloud/data/ || true
|
||||||
|
touch /var/lib/cloud/data/result.json
|
||||||
|
|
||||||
|
- name: Create a stackscript
|
||||||
|
linode_stackscript_v4:
|
||||||
|
access_token: "{{ algo_linode_token }}"
|
||||||
|
label: "{{ algo_server_name }}"
|
||||||
|
state: present
|
||||||
|
description: Environment:Algo
|
||||||
|
images:
|
||||||
|
- "{{ cloud_providers.linode.image }}"
|
||||||
|
script: |
|
||||||
|
{{ stackscript }}
|
||||||
|
register: _linode_stackscript
|
||||||
|
|
||||||
|
- name: Update the stackscript
|
||||||
|
uri:
|
||||||
|
url: https://api.linode.com/v4/linode/stackscripts/{{ _linode_stackscript.stackscript.id }}
|
||||||
|
method: PUT
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
script: |
|
||||||
|
{{ stackscript }}
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{ algo_linode_token }}
|
||||||
|
when: (_linode_stackscript.stackscript.script | hash('md5')) != (stackscript | hash('md5'))
|
||||||
|
|
||||||
|
- name: Creating an instance...
|
||||||
|
linode_v4:
|
||||||
|
access_token: "{{ algo_linode_token }}"
|
||||||
|
label: "{{ algo_server_name }}"
|
||||||
|
state: present
|
||||||
|
region: "{{ algo_linode_region }}"
|
||||||
|
image: "{{ cloud_providers.linode.image }}"
|
||||||
|
type: "{{ cloud_providers.linode.type }}"
|
||||||
|
authorized_keys: "{{ public_key }}"
|
||||||
|
stackscript_id: "{{ _linode_stackscript.stackscript.id }}"
|
||||||
|
register: _linode
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
cloud_instance_ip: "{{ _linode.instance.ipv4[0] }}"
|
||||||
|
ansible_ssh_user: algo
|
||||||
|
ansible_ssh_port: "{{ ssh_port }}"
|
||||||
|
cloudinit: true
|
@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
- pause:
|
||||||
|
prompt: |
|
||||||
|
Enter your ACCESS token. (https://developers.linode.com/api/v4/#access-and-authentication):
|
||||||
|
echo: false
|
||||||
|
register: _linode_token
|
||||||
|
when:
|
||||||
|
- linode_token is undefined
|
||||||
|
- lookup('env','LINODE_API_TOKEN')|length <= 0
|
||||||
|
|
||||||
|
- name: Set the token as a fact
|
||||||
|
set_fact:
|
||||||
|
algo_linode_token: "{{ linode_token | default(_linode_token.user_input|default(None)) | default(lookup('env','LINODE_API_TOKEN'), true) }}"
|
||||||
|
|
||||||
|
- name: Get regions
|
||||||
|
uri:
|
||||||
|
url: https://api.linode.com/v4/regions
|
||||||
|
method: GET
|
||||||
|
status_code: 200
|
||||||
|
register: _linode_regions
|
||||||
|
|
||||||
|
- name: Set facts about the regions
|
||||||
|
set_fact:
|
||||||
|
linode_regions: "{{ _linode_regions.json.data | sort(attribute='id') }}"
|
||||||
|
|
||||||
|
- name: Set default region
|
||||||
|
set_fact:
|
||||||
|
default_region: >-
|
||||||
|
{% for r in linode_regions %}
|
||||||
|
{%- if r['id'] == "us-east" %}{{ loop.index }}{% endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
- pause:
|
||||||
|
prompt: |
|
||||||
|
What region should the server be located in?
|
||||||
|
{% for r in linode_regions %}
|
||||||
|
{{ loop.index }}. {{ r['id'] }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
Enter the number of your desired region
|
||||||
|
[{{ default_region }}]
|
||||||
|
register: _algo_region
|
||||||
|
when: region is undefined
|
||||||
|
|
||||||
|
- name: Set additional facts
|
||||||
|
set_fact:
|
||||||
|
algo_linode_region: >-
|
||||||
|
{% if region is defined %}{{ region }}
|
||||||
|
{%- elif _algo_region.user_input %}{{ linode_regions[_algo_region.user_input | int -1 ]['id'] }}
|
||||||
|
{%- else %}{{ linode_regions[default_region | int - 1]['id'] }}{% endif %}
|
||||||
|
public_key: "{{ lookup('file', '{{ SSH_keys.public }}') }}"
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
- name: Install requirements
|
||||||
|
pip:
|
||||||
|
name:
|
||||||
|
- linode_api4
|
||||||
|
state: latest
|
||||||
|
virtualenv_python: python3
|
@ -1,2 +1,2 @@
|
|||||||
---
|
---
|
||||||
ssh_tunnels_config_path: "configs/{{ IP_subject_alt_name }}/ssh-tunnel/"
|
ssh_tunnels_config_path: configs/{{ IP_subject_alt_name }}/ssh-tunnel/
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
|
---
|
||||||
- name: restart ssh
|
- name: restart ssh
|
||||||
service: name="{{ ssh_service_name|default('ssh') }}" state=restarted
|
service: name="{{ ssh_service_name|default('ssh') }}" state=restarted
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue