Skip to content

Commit 7710154

Browse files
committed
use builder for passwords
1 parent 90299b3 commit 7710154

14 files changed

Lines changed: 635 additions & 344 deletions

File tree

docs/secret/passwords.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Passwords
2+
3+
Passwords are a part for the Secret Manager service. The service allows to generate and manage passwords, store them in specified storage and use them for different purposes.
4+
5+
The current implementation supports three methods to create passwords: `AUTO_HEX`, `AUTO_URL_SAFE` and `MANUAL`.
6+
7+
Examples:
8+
9+
```bash
10+
curl --location 'http://10.20.0.2:11010/v1/secret/passwords/' \
11+
--header 'Content-Type: application/json' \
12+
--header 'Authorization: Bearer MY_TOKEN' \
13+
--data-raw '{
14+
"name": "my-password",
15+
"project_id": "00000000-0000-0000-0000-000000000000",
16+
"method": "AUTO_HEX",
17+
"constructor": {
18+
"kind": "plain"
19+
},
20+
"default_length": 32
21+
}'
22+
```
23+
24+
The main fields are:
25+
26+
- **name** - name of the password.
27+
- **project_id** - it's a project the password belongs to.
28+
- **method** - the method to generate or specify the password value.
29+
- **constructor** - in the context of the passwords, the constructor object creates and stores the password. The `plain` means create and store in the plain format.
30+
- **default_length** - the length of the auto-generated password value (default is `32`, maximum is `512`).
31+
32+
## Examples for passwords in manifest
33+
34+
### AUTO_HEX
35+
36+
```yaml
37+
# AUTO_HEX generates a random hex-encoded string via secrets.token_hex()
38+
# After processing, target value will be something like:
39+
# value: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
40+
auto_hex_password:
41+
name: "auto-hex-password"
42+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
43+
method: "AUTO_HEX"
44+
constructor:
45+
kind: plain
46+
default_length: 32
47+
```
48+
49+
### AUTO_URL_SAFE
50+
51+
```yaml
52+
# AUTO_URL_SAFE generates a random URL-safe base64 string via secrets.token_urlsafe()
53+
# After processing, target value will be something like:
54+
# value: "a1b2c3d4-e5f6_a7b8c9d0e1f2a3b4c5d6a1b2c3d4e5f6a7b8"
55+
auto_url_safe_password:
56+
name: "auto-url-safe-password"
57+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
58+
method: "AUTO_URL_SAFE"
59+
constructor:
60+
kind: plain
61+
default_length: 48
62+
```
63+
64+
### MANUAL
65+
66+
```yaml
67+
# MANUAL uses the exact value provided by the user
68+
# After processing, target value will be:
69+
# value: "my-strong-password-value"
70+
manual_password:
71+
name: "manual-password"
72+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
73+
method: "MANUAL"
74+
constructor:
75+
kind: plain
76+
value: "my-strong-password-value"
77+
```
78+
79+
### LONG_AUTO_HEX
80+
81+
```yaml
82+
# AUTO_HEX with longer length
83+
# After processing, target value will be something like:
84+
# value: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6..."
85+
long_auto_hex_password:
86+
name: "long-auto-hex-password"
87+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
88+
method: "AUTO_HEX"
89+
constructor:
90+
kind: plain
91+
default_length: 128
92+
```
93+
94+
## Methods / Generation Strategies
95+
96+
The Exordos Core supports the following methods to generate or provide passwords:
97+
98+
### AUTO_HEX
99+
100+
The `AUTO_HEX` method auto-generates a random hex-encoded string. The password value is generated using Python's `secrets.token_hex()` function. This is the default method.
101+
102+
```bash
103+
curl --location 'http://10.20.0.2:11010/v1/secret/passwords/' \
104+
--header 'Content-Type: application/json' \
105+
--header 'Authorization: Bearer MY_TOKEN' \
106+
--data-raw '{
107+
"name": "auto-hex-password",
108+
"project_id": "00000000-0000-0000-0000-000000000000",
109+
"method": "AUTO_HEX",
110+
"constructor": {
111+
"kind": "plain"
112+
},
113+
"default_length": 48
114+
}'
115+
```
116+
117+
### AUTO_URL_SAFE
118+
119+
The `AUTO_URL_SAFE` method auto-generates a random URL-safe base64-encoded string. The password value is generated using Python's `secrets.token_urlsafe()` function.
120+
121+
```bash
122+
curl --location 'http://10.20.0.2:11010/v1/secret/passwords/' \
123+
--header 'Content-Type: application/json' \
124+
--header 'Authorization: Bearer MY_TOKEN' \
125+
--data-raw '{
126+
"name": "auto-url-safe-password",
127+
"project_id": "00000000-0000-0000-0000-000000000000",
128+
"method": "AUTO_URL_SAFE",
129+
"constructor": {
130+
"kind": "plain"
131+
},
132+
"default_length": 32
133+
}'
134+
```
135+
136+
### MANUAL
137+
138+
The `MANUAL` method allows to specify a custom password value explicitly. When using this method, the `value` field is required and must be provided in the request.
139+
140+
```bash
141+
curl --location 'http://10.20.0.2:11010/v1/secret/passwords/' \
142+
--header 'Content-Type: application/json' \
143+
--header 'Authorization: Bearer MY_TOKEN' \
144+
--data-raw '{
145+
"name": "manual-password",
146+
"project_id": "00000000-0000-0000-0000-000000000000",
147+
"method": "MANUAL",
148+
"constructor": {
149+
"kind": "plain"
150+
},
151+
"value": "my-strong-password-value"
152+
}'
153+
```
154+
155+
## Status Lifecycle
156+
157+
A password goes through the following statuses during its lifecycle:
158+
159+
- **NEW** - the password has been created and is waiting to be processed.
160+
- **IN_PROGRESS** - the password value is being generated or stored by the agent.
161+
- **ACTIVE** - the password has been successfully generated and is ready to use.
162+
- **ERROR** - an error occurred during password processing.
163+
164+
The `status` field is read-only and automatically managed by the system.
165+
166+
## Updating a Password
167+
168+
When updating a password, the status is automatically reset to `NEW` to trigger regeneration of the value.
169+
170+
```bash
171+
curl --location --request PUT 'http://10.20.0.2:11010/v1/secret/passwords/<PASSWORD_UUID>' \
172+
--header 'Content-Type: application/json' \
173+
--header 'Authorization: Bearer MY_TOKEN' \
174+
--data-raw '{
175+
"default_length": 48
176+
}'
177+
```
178+
179+
## Deleting a Password
180+
181+
```bash
182+
curl --location --request DELETE 'http://10.20.0.2:11010/v1/secret/passwords/<PASSWORD_UUID>' \
183+
--header 'Authorization: Bearer MY_TOKEN'
184+
```
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: "passwords-example"
2+
uuid: "e7b3cd9a-1f45-4bc1-a3b8-3fe40124bbbb"
3+
description: "Exordos passwords example"
4+
schema_version: 1
5+
version: "0.0.1"
6+
api_version: "v1"
7+
8+
requirements:
9+
core:
10+
from_version: "0.0.0"
11+
12+
resources:
13+
$core.secret.passwords:
14+
# AUTO_HEX generates a random hex-encoded string via secrets.token_hex()
15+
# After processing, target value will be something like:
16+
# value: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
17+
auto_hex_password:
18+
name: "auto-hex-password"
19+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
20+
method: "AUTO_HEX"
21+
constructor:
22+
kind: plain
23+
default_length: 32
24+
25+
# AUTO_URL_SAFE generates a random URL-safe base64 string via secrets.token_urlsafe()
26+
# After processing, target value will be something like:
27+
# value: "a1b2c3d4-e5f6_a7b8c9d0e1f2a3b4c5d6a1b2c3d4e5f6a7b8"
28+
auto_url_safe_password:
29+
name: "auto-url-safe-password"
30+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
31+
method: "AUTO_URL_SAFE"
32+
constructor:
33+
kind: plain
34+
default_length: 48
35+
36+
# MANUAL uses the exact value provided by the user
37+
# After processing, target value will be:
38+
# value: "my-strong-password-value"
39+
manual_password:
40+
name: "manual-password"
41+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
42+
method: "MANUAL"
43+
constructor:
44+
kind: plain
45+
value: "my-strong-password-value"
46+
47+
# AUTO_HEX with longer length
48+
# After processing, target value will be something like:
49+
# value: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6..."
50+
long_auto_hex_password:
51+
name: "long-auto-hex-password"
52+
project_id: "12345678-c625-4fee-81d5-f691897b8142"
53+
method: "AUTO_HEX"
54+
constructor:
55+
kind: plain
56+
default_length: 128

exordos_core/agent/universal/drivers/secret/password.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
class PasswordCapabilityDriver(direct.DirectAgentDriver):
3333
"""Password capability driver."""
3434

35-
def __init__(self):
36-
storage = fs.TargetFieldsFileStorage(PASSWORD_TARGET_FIELDS_STORAGE)
35+
def __init__(self, storage_path: str = PASSWORD_TARGET_FIELDS_STORAGE):
36+
storage = fs.TargetFieldsFileStorage(storage_path)
3737
client = db_back.DatabaseSecretBackendClient()
3838

3939
super().__init__(storage=storage, client=client)

exordos_core/gservice/service.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from exordos_core.network.lb.builders import paas as net_lb_paas
5353
from exordos_core.network.lb.dm import models as lb_models
5454
from exordos_core.secret import service as secret_service
55+
from exordos_core.secret.builders import service as password_builder_svc
5556
from exordos_core.telemetry import service as telemetry_service
5657
from exordos_core.vs.builders import service as vs_builder_svc
5758

@@ -186,6 +187,7 @@ def __init__(self, iter_min_period=3, iter_pause=0.1):
186187
secret_svc = secret_service.SecretServiceBuilder(
187188
iter_min_period=iter_min_period,
188189
)
190+
password_builder = password_builder_svc.PasswordBuilder()
189191
# Build the event sender only when event delivery is enabled (the
190192
# exordos_notification element drops an [events] override into the
191193
# config dir). When disabled, build_from_config would receive a None
@@ -225,6 +227,7 @@ def __init__(self, iter_min_period=3, iter_pause=0.1):
225227
net_lb_iaas_builder,
226228
net_lb_paas_builder,
227229
secret_svc,
230+
password_builder,
228231
em_builder,
229232
dns_sync,
230233
# non-essential services should be last

exordos_core/secret/builders/__init__.py

Whitespace-only changes.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright 2025-2026 Genesis Corporation.
2+
#
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
import logging
18+
19+
from gcl_sdk.agents.universal.dm import models as ua_models
20+
from gcl_sdk.agents.universal.services import builder as sdk_builder
21+
22+
from exordos_core.secret import constants as sc
23+
from exordos_core.secret.dm import models
24+
25+
LOG = logging.getLogger(__name__)
26+
27+
28+
class Password(
29+
models.Password,
30+
ua_models.InstanceMixin,
31+
ua_models.KindAwareMixin,
32+
):
33+
pass
34+
35+
@classmethod
36+
def get_resource_kind(cls) -> str:
37+
return sc.PASSWORD_KIND
38+
39+
40+
class Certificate(
41+
models.Certificate,
42+
ua_models.InstanceMixin,
43+
ua_models.KindAwareMixin,
44+
):
45+
pass
46+
47+
@classmethod
48+
def get_resource_kind(cls) -> str:
49+
return sc.CERTIFICATE_KIND
50+
51+
52+
class RSAKey(
53+
models.RSAKey,
54+
ua_models.InstanceMixin,
55+
ua_models.KindAwareMixin,
56+
):
57+
pass
58+
59+
@classmethod
60+
def get_resource_kind(cls) -> str:
61+
return sc.RSA_KEY_KIND
62+
63+
64+
class SSHKey(
65+
models.SSHKey,
66+
ua_models.InstanceMixin,
67+
ua_models.KindAwareMixin,
68+
):
69+
pass
70+
71+
@classmethod
72+
def get_resource_kind(cls) -> str:
73+
return sc.SSH_KEY_KIND
74+
75+
76+
class PasswordBuilder(sdk_builder.UniversalBuilderService):
77+
def __init__(
78+
self,
79+
iter_min_period: int = 1,
80+
iter_pause: float = 0.1,
81+
) -> None:
82+
super().__init__(
83+
instance_model=Password,
84+
iter_min_period=iter_min_period,
85+
iter_pause=iter_pause,
86+
)
87+
88+
def actualize_outdated_instance(
89+
self,
90+
current_instance: Password,
91+
actual_instance: Password,
92+
) -> None:
93+
password_updated = False
94+
status_updated = False
95+
if actual_instance.status != current_instance.status:
96+
current_instance.status = actual_instance.status
97+
status_updated = True
98+
if actual_instance.value != current_instance.value:
99+
current_instance.value = actual_instance.value
100+
password_updated = True
101+
if status_updated or password_updated:
102+
current_instance.save()

0 commit comments

Comments
 (0)