Skip to content

Commit 15002d2

Browse files
authored
Release 1.1.0 (#8)
* feat: add support for fetching vaults and handling optional vault ID and secrets file in secret retrieval * chore: refactor functions into separate utilities * feat: add fetch_server module for retrieving server info and vaults * feat: update README and improve error handling in server communication
1 parent 7bcb972 commit 15002d2

File tree

8 files changed

+363
-120
lines changed

8 files changed

+363
-120
lines changed

.github/workflows/galaxy_publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Publish to Ansible Galaxy
22

33
on:
4+
release:
45
workflow_dispatch:
56

67
jobs:

README.md

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
# DVLS Ansible Module
22

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

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

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

16+
Set the following environment variables for DVLS authentication:
1317
```sh
1418
export DVLS_APP_KEY="your_app_key_here"
1519
export DVLS_APP_SECRET="your_app_secret_here"
1620
```
1721

18-
## Usage
22+
## Usage with static secrets file
1923

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

3337
```yaml
34-
---
35-
- name: Fetch secrets from DVLS
36-
hosts: localhost
3738
vars_files:
3839
- secrets.yml
3940
tasks:
4041
- name: Fetch secrets
4142
devolutions.dvls.fetch_secrets:
42-
server_base_url: "https://example.yourcompagny.com"
43+
server_base_url: "https://example.yourcompany.com"
4344
app_key: "{{ lookup('env', 'DVLS_APP_KEY') }}"
4445
app_secret: "{{ lookup('env', 'DVLS_APP_SECRET') }}"
4546
vault_id: "00000000-0000-0000-0000-000000000000"
@@ -55,41 +56,91 @@ Use the following playbook to authenticate with DVLS and fetch the secrets defin
5556
msg: "{{ secrets['name-or-id'].value }}"
5657
```
5758

58-
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:
59+
## Usage fetching all secrets
60+
61+
### Example playbook.yml using a VaultID
62+
Use the following playbook to authenticate with DVLS and fetch every secrets from a defined VaultID:
63+
64+
```yaml
65+
tasks:
66+
- name: Fetch secrets
67+
devolutions.dvls.fetch_secrets:
68+
server_base_url: "https://example.yourcompany.com"
69+
app_key: "{{ lookup('env', 'DVLS_APP_KEY') }}"
70+
app_secret: "{{ lookup('env', 'DVLS_APP_SECRET') }}"
71+
vault_id: "00000000-0000-0000-0000-000000000000"
72+
register: secrets
73+
74+
- name: Dump secrets
75+
debug:
76+
msg: "{{ secrets }}"
77+
78+
- name: Dump a secret
79+
debug:
80+
msg: "{{ secrets['name-or-id'].value }}"
81+
```
82+
83+
## Usage fetching server info and vaults list
84+
85+
```yaml
86+
---
87+
- name: Fetch dvls server information
88+
server:
89+
server_base_url: "https://example.yourcompany.com"
90+
app_key: "{{ lookup('env', 'DVLS_APP_KEY') }}"
91+
app_secret: "{{ lookup('env', 'DVLS_APP_SECRET') }}"
92+
register: server
93+
94+
- name: Fetch URI
95+
debug:
96+
msg: "{{ server.accessURI }}"
97+
98+
- name: Fetch a vault from the list
99+
debug:
100+
msg: "{{ server.vaults[1].id }}"
101+
```
102+
103+
Example response
59104

60105
```json
61-
"Username and password": {
62-
"domain": "",
63-
"password": "",
64-
"username": ""
65-
},
66-
"Connection string": {
67-
"connectionString": ""
68-
},
69-
"Secret": {
70-
"password": ""
71-
},
72-
"API key": {
73-
"apiId": "",
74-
"apiKey": "",
75-
"tenantId": ""
76-
},
77-
"SSH key": {
78-
"domain": "",
79-
"password": "",
80-
"privateKeyData": "",
81-
"privateKeyOverridePassword": "",
82-
"privateKeyPassPhrase": "",
83-
"publicKeyData": "",
84-
"username": ""
85-
},
86-
"Azure service principal": {
87-
"clientId": "",
88-
"clientSecret": "",
89-
"tenantId": ""
90-
},
106+
{
107+
"server": {
108+
"accessURI": "https://example.dvls-server.com/",
109+
"changed": false,
110+
"expirationDate": "2030-12-31T23:59:59",
111+
"failed": false,
112+
"vaults": [
113+
{
114+
"description": "User vault for personal entries",
115+
"id": "123e4567-e89b-12d3-a456-426614174000",
116+
"type": "User"
117+
},
118+
{
119+
"description": "Shared vault for organization",
120+
"id": "987f6543-d21c-43ba-987f-123456789abc",
121+
"name": "Organization vault",
122+
"type": "Shared"
123+
}
124+
],
125+
"version": "2025.1.0.0"
126+
}
127+
}
91128
```
92129

130+
## Secrets definition
131+
132+
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:
133+
134+
| **Category** | **Fields** |
135+
|---------------------------|---------------------------------------------------------------------------|
136+
| Username and password | `domain`, `password`, `username` |
137+
| Connection string | `connectionString` |
138+
| Secret | `password` |
139+
| API key | `apiId`, `apiKey`, `tenantId` |
140+
| SSH key | `domain`, `password`, `privateKeyData`, `privateKeyOverridePassword`, `privateKeyPassPhrase`, `publicKeyData`, `username` |
141+
| Azure service principal | `clientId`, `clientSecret`, `tenantId` |
142+
143+
93144
### Example using secret value
94145
For example, if you want to access the ```apiId``` from an ```API key secret```, you would use the following syntax:
95146

galaxy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
namespace: devolutions
22
name: dvls
3-
version: 1.0.1
3+
version: 1.1.0
44
readme: README.md
55
authors:
66
- Danny Bédard <[email protected]>
7-
description: This Ansible module allows you to authenticate with DVLS (Devolutions Server) and fetch secrets by name or ID.
7+
description: This Ansible module allows you to authenticate with DVLS and fetch server information, vaults and secrets by name or ID.
88
license: MIT
99
license_file: ''
1010
tags: [dvls, secrets, devolutions, password]

plugins/module_utils/auth.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
import requests
3+
import json
4+
5+
def login(server_base_url, app_key, app_secret):
6+
login_url = f"{server_base_url}/api/v1/login"
7+
login_data = {
8+
"appKey": app_key,
9+
"appSecret": app_secret
10+
}
11+
login_headers = {
12+
"Content-Type": "application/json"
13+
}
14+
15+
try:
16+
response = requests.post(login_url, headers=login_headers, data=json.dumps(login_data))
17+
response.raise_for_status()
18+
except Exception as e:
19+
raise Exception(f"Failed to login: Unable to reach the server. Verify your network connection and server URL: {e}")
20+
21+
auth_response = response.json()
22+
token = auth_response.get('tokenId')
23+
24+
if not token or token == "null":
25+
raise Exception("Failed to login or obtain token.")
26+
27+
return token
28+
29+
def logout(server_base_url, token):
30+
logout_url = f"{server_base_url}/api/v1/logout"
31+
logout_headers = {
32+
"Content-Type": "application/json",
33+
"tokenId": token
34+
}
35+
36+
requests.post(logout_url, headers=logout_headers)
37+
return None

plugins/module_utils/server.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import requests
2+
import json
3+
4+
def public_instance_information(server_base_url, token):
5+
url = f"{server_base_url}/api/public-instance-information"
6+
headers = {
7+
"Content-Type": "application/json",
8+
"tokenId": token
9+
}
10+
11+
try:
12+
response = requests.get(url, headers=headers)
13+
return response.json()
14+
except Exception as e:
15+
raise Exception(f"An error occurred while fetching public instance information: {e}")
16+
17+
def private_instance_information(server_base_url, token):
18+
url = f"{server_base_url}/api/private-instance-information"
19+
headers = {
20+
"Content-Type": "application/json",
21+
"tokenId": token
22+
}
23+
24+
try:
25+
response = requests.get(url, headers=headers)
26+
response.raise_for_status()
27+
return response.json()
28+
except Exception as e:
29+
raise Exception(f"An error occurred while fetching private instance information: {e}")

plugins/module_utils/vaults.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import requests
2+
import json
3+
4+
def get_vaults(server_base_url, token):
5+
vaults_url = f"{server_base_url}/api/v1/vault"
6+
vaults_headers = {
7+
"Content-Type": "application/json",
8+
"tokenId": token
9+
}
10+
11+
try:
12+
response = requests.get(vaults_url, headers=vaults_headers)
13+
response.raise_for_status()
14+
15+
json_data = response.json()
16+
if 'data' not in json_data:
17+
raise ValueError(f"'data' key missing in response: {json_data}")
18+
19+
return json_data.get('data', [])
20+
except Exception as e:
21+
raise Exception(f"An error occurred while getting vaults: {e}")
22+
23+
def get_vault_entry(server_base_url, token, vault_id, entry_id):
24+
vault_url = f"{server_base_url}/api/v1/vault/{vault_id}/entry/{entry_id}"
25+
vault_headers = {
26+
"Content-Type": "application/json",
27+
"tokenId": token
28+
}
29+
30+
try:
31+
response = requests.get(vault_url, headers=vault_headers)
32+
response.raise_for_status()
33+
34+
return response.json()
35+
except Exception as e:
36+
raise Exception(f"An error occurred while getting a vault entry: {e}")
37+
38+
def get_vault_entries(server_base_url, token, vault_id):
39+
vault_url = f"{server_base_url}/api/v1/vault/{vault_id}/entry"
40+
vault_headers = {
41+
"Content-Type": "application/json",
42+
"tokenId": token
43+
}
44+
45+
try:
46+
response = requests.get(vault_url, headers=vault_headers)
47+
response.raise_for_status()
48+
49+
json_data = response.json()
50+
if 'data' not in json_data:
51+
raise ValueError(f"'data' key missing in response: {json_data}")
52+
53+
return json_data.get('data', [])
54+
except Exception as e:
55+
raise Exception(f"An error occurred while getting vault entries: {e}")

0 commit comments

Comments
 (0)