Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/galaxy_publish.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Publish to Ansible Galaxy

on:
release:
workflow_dispatch:

jobs:
Expand Down
135 changes: 93 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
# DVLS Ansible Module

This Ansible module allows you to authenticate with DVLS and fetch secrets by name or ID.
This Ansible module allows you to authenticate with DVLS and fetch server information, vaults, and secrets.

## Requirements
## Features
- Authenticate with DVLS using application identities.
- Fetch server information, vault lists, or specific secrets.
- Flexible support for static secrets or fetching all secrets in a vault.

## Requirements
- Ansible
- Python requests library
- You must have a DVLS application identities, it can be created at {your-dvls-url}/administration/applications
- This application must have permission to fetch the desired secrets
- Set the necessary environment variables for DVLS authentication:
- Python `requests` library
- A DVLS application identity (create at `{your-dvls-url}/administration/applications`).
- The application must have permissions to fetch the desired secrets.

Set the following environment variables for DVLS authentication:
```sh
export DVLS_APP_KEY="your_app_key_here"
export DVLS_APP_SECRET="your_app_secret_here"
```

## Usage
## Usage with static secrets file

### Example secrets.yml
Define the secrets you want to fetch in ```secrets.yml```:
Expand All @@ -31,15 +35,12 @@ secrets:
Use the following playbook to authenticate with DVLS and fetch the secrets defined in ```secrets.yml```:

```yaml
---
- name: Fetch secrets from DVLS
hosts: localhost
vars_files:
- secrets.yml
tasks:
- name: Fetch secrets
devolutions.dvls.fetch_secrets:
server_base_url: "https://example.yourcompagny.com"
server_base_url: "https://example.yourcompany.com"
app_key: "{{ lookup('env', 'DVLS_APP_KEY') }}"
app_secret: "{{ lookup('env', 'DVLS_APP_SECRET') }}"
vault_id: "00000000-0000-0000-0000-000000000000"
Expand All @@ -55,41 +56,91 @@ Use the following playbook to authenticate with DVLS and fetch the secrets defin
msg: "{{ secrets['name-or-id'].value }}"
```

To access a particular field within a secret, you can use the format ```{{ secrets['name-or-id'].value }}```. Here’s a breakdown of the available categories and their fields:
## Usage fetching all secrets

### Example playbook.yml using a VaultID
Use the following playbook to authenticate with DVLS and fetch every secrets from a defined VaultID:

```yaml
tasks:
- name: Fetch secrets
devolutions.dvls.fetch_secrets:
server_base_url: "https://example.yourcompany.com"
app_key: "{{ lookup('env', 'DVLS_APP_KEY') }}"
app_secret: "{{ lookup('env', 'DVLS_APP_SECRET') }}"
vault_id: "00000000-0000-0000-0000-000000000000"
register: secrets

- name: Dump secrets
debug:
msg: "{{ secrets }}"

- name: Dump a secret
debug:
msg: "{{ secrets['name-or-id'].value }}"
```

## Usage fetching server info and vaults list

```yaml
---
- name: Fetch dvls server information
server:
server_base_url: "https://example.yourcompany.com"
app_key: "{{ lookup('env', 'DVLS_APP_KEY') }}"
app_secret: "{{ lookup('env', 'DVLS_APP_SECRET') }}"
register: server

- name: Fetch URI
debug:
msg: "{{ server.accessURI }}"

- name: Fetch a vault from the list
debug:
msg: "{{ server.vaults[1].id }}"
```

Example response

```json
"Username and password": {
"domain": "",
"password": "",
"username": ""
},
"Connection string": {
"connectionString": ""
},
"Secret": {
"password": ""
},
"API key": {
"apiId": "",
"apiKey": "",
"tenantId": ""
},
"SSH key": {
"domain": "",
"password": "",
"privateKeyData": "",
"privateKeyOverridePassword": "",
"privateKeyPassPhrase": "",
"publicKeyData": "",
"username": ""
},
"Azure service principal": {
"clientId": "",
"clientSecret": "",
"tenantId": ""
},
{
"server": {
"accessURI": "https://example.dvls-server.com/",
"changed": false,
"expirationDate": "2030-12-31T23:59:59",
"failed": false,
"vaults": [
{
"description": "User vault for personal entries",
"id": "123e4567-e89b-12d3-a456-426614174000",
"type": "User"
},
{
"description": "Shared vault for organization",
"id": "987f6543-d21c-43ba-987f-123456789abc",
"name": "Organization vault",
"type": "Shared"
}
],
"version": "2025.1.0.0"
}
}
```

## Secrets definition

To access a particular field within a secret, you can use the format ```{{ secrets['name-or-id'].value }}```. Here’s a breakdown of the available categories and their fields:

| **Category** | **Fields** |
|---------------------------|---------------------------------------------------------------------------|
| Username and password | `domain`, `password`, `username` |
| Connection string | `connectionString` |
| Secret | `password` |
| API key | `apiId`, `apiKey`, `tenantId` |
| SSH key | `domain`, `password`, `privateKeyData`, `privateKeyOverridePassword`, `privateKeyPassPhrase`, `publicKeyData`, `username` |
| Azure service principal | `clientId`, `clientSecret`, `tenantId` |


### Example using secret value
For example, if you want to access the ```apiId``` from an ```API key secret```, you would use the following syntax:

Expand Down
4 changes: 2 additions & 2 deletions galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
namespace: devolutions
name: dvls
version: 1.0.1
version: 1.1.0
readme: README.md
authors:
- Danny Bédard <devops@devolutions.net>
description: This Ansible module allows you to authenticate with DVLS (Devolutions Server) and fetch secrets by name or ID.
description: This Ansible module allows you to authenticate with DVLS and fetch server information, vaults and secrets by name or ID.
license: MIT
license_file: ''
tags: [dvls, secrets, devolutions, password]
Expand Down
37 changes: 37 additions & 0 deletions plugins/module_utils/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
import requests
import json

def login(server_base_url, app_key, app_secret):
login_url = f"{server_base_url}/api/v1/login"
login_data = {
"appKey": app_key,
"appSecret": app_secret
}
login_headers = {
"Content-Type": "application/json"
}

try:
response = requests.post(login_url, headers=login_headers, data=json.dumps(login_data))
response.raise_for_status()
except Exception as e:
raise Exception(f"Failed to login: Unable to reach the server. Verify your network connection and server URL: {e}")

auth_response = response.json()
token = auth_response.get('tokenId')

if not token or token == "null":
raise Exception("Failed to login or obtain token.")

return token

def logout(server_base_url, token):
logout_url = f"{server_base_url}/api/v1/logout"
logout_headers = {
"Content-Type": "application/json",
"tokenId": token
}

requests.post(logout_url, headers=logout_headers)
return None
29 changes: 29 additions & 0 deletions plugins/module_utils/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import requests
import json

def public_instance_information(server_base_url, token):
url = f"{server_base_url}/api/public-instance-information"
headers = {
"Content-Type": "application/json",
"tokenId": token
}

try:
response = requests.get(url, headers=headers)
return response.json()
except Exception as e:
raise Exception(f"An error occurred while fetching public instance information: {e}")

def private_instance_information(server_base_url, token):
url = f"{server_base_url}/api/private-instance-information"
headers = {
"Content-Type": "application/json",
"tokenId": token
}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
except Exception as e:
raise Exception(f"An error occurred while fetching private instance information: {e}")
55 changes: 55 additions & 0 deletions plugins/module_utils/vaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import requests
import json

def get_vaults(server_base_url, token):
vaults_url = f"{server_base_url}/api/v1/vault"
vaults_headers = {
"Content-Type": "application/json",
"tokenId": token
}

try:
response = requests.get(vaults_url, headers=vaults_headers)
response.raise_for_status()

json_data = response.json()
if 'data' not in json_data:
raise ValueError(f"'data' key missing in response: {json_data}")

return json_data.get('data', [])
except Exception as e:
raise Exception(f"An error occurred while getting vaults: {e}")

def get_vault_entry(server_base_url, token, vault_id, entry_id):
vault_url = f"{server_base_url}/api/v1/vault/{vault_id}/entry/{entry_id}"
vault_headers = {
"Content-Type": "application/json",
"tokenId": token
}

try:
response = requests.get(vault_url, headers=vault_headers)
response.raise_for_status()

return response.json()
except Exception as e:
raise Exception(f"An error occurred while getting a vault entry: {e}")

def get_vault_entries(server_base_url, token, vault_id):
vault_url = f"{server_base_url}/api/v1/vault/{vault_id}/entry"
vault_headers = {
"Content-Type": "application/json",
"tokenId": token
}

try:
response = requests.get(vault_url, headers=vault_headers)
response.raise_for_status()

json_data = response.json()
if 'data' not in json_data:
raise ValueError(f"'data' key missing in response: {json_data}")

return json_data.get('data', [])
except Exception as e:
raise Exception(f"An error occurred while getting vault entries: {e}")
Loading