Struktura wtyczki¶
Wtyczka stanowi archiwum plików, w skład którego wchodzą:
Ostrzeżenie
Rozmiar skompresowanego archiwum nie może przekraczać 10 MB. Po rozpakowaniu, sumaryczny rozmiar plików wtyczki nie może przekraczać 100 MB.
manifest.json¶
Manifest deklaruje kluczowe meta dane wtyczki oraz zmienne wykorzystywane przez modyfikator oraz weryfikator haseł.
Parametr | Opis |
---|---|
name |
Unikatowa nazwa umożliwiająca jednoznaczną identyfikację wtyczki. |
plugin_version |
Wersja wtyczki. Informacja Producent sugeruje zastosowanie wersjonowania semantycznego MAJOR.MINOR.PATCH opisanego na stronie https://semver.org/. |
type |
Zarówno w przypadku modyfikatora jak i weryfikatora, typem powinien być password_changer . |
engine_version |
Fudo PAM zapewnia środowisko wykonywania wtyczek w określonej wersji. Wtyczka wymaga zadeklarowania kompatybilnej wersji silnika. |
timeout |
Maksymalny czas wykonania skryptu, wyrażony w sekundach. W przypadku gdy skrypt modyfikujący/weryfikujący hasło nie wykona się prawidłowo w wyznaczonym czasie, proces odpowiedzialny za jego wykonanie zostanie zakończony a próba zmiany/weryfikacji hasła zostanie uznana za nieudaną. |
Manifest definiuje także listę zmiennych modyfikatora i weryfikatora haseł odpowiednio w sekcji change
i verify
. Zmienne mogą odwoływać się do obiektów modelu danych lub być definiowane ręcznie. Definicja zmiennej opisana jest następującą strukturą:
Parametr | Typ | Wymagany | Opis |
---|---|---|---|
name |
string | Nazwa zmiennej. | |
description |
string | Opis zmiennej. | |
required |
boolean | Specyfikacja obowiązku zdefiniowania zmiennej. | |
object_type |
string | Typ obiektu, do której odwołuje się zmienna. | |
object_property |
string | Parametr obiektu, którego wartość zostanie użyta do zainicjowania zmiennej. | |
encrypt |
boolean | ? | Specyfikator szyfrowania zmiennej w bazie danych. Wymagane, w przypadku gdy nie zostały określone object_type oraz object_property . |
Dostępne obiekty i ich właściwości
Obiekt/parametr | Opis |
---|---|
server |
Obiekt serwer zdefiniowany w lokalnej bazie danych. |
|
Nazwa obiektu. |
|
Adres IP Fudo PAM wykorzystany do komunikacji z serwerem. |
|
Certyfikat CA. |
|
Numer portu, na którym nasłuchuje serwer docelowy. |
|
Protokół komunikacji z serwerem docelowym: citrixsf , http , ica , modbus , mysql , oracle , rdp , ssh , system , tcp , tds , telnet , tn3270 , tn5250 , vnc . |
|
Protokół bezpieczeństwa używany przez serwer RDP: nla , tls , std . |
|
1 jeśli serwer korzysta z szyfrowania SSL/TLS, 0 jeśli SSL/TLS nie jest wykorzystywany. |
|
1 jeśli protokół SSL w wersji 2.0 może zostać użyty do komunikacji z serwerem docelowym, 0 jeśli komunikacja z serwerem poprzez protokół SSL 2.0 nie jest dopuszczalna. |
|
1 jeśli protokół SSL w wersji 3.0 może zostać użyty do komunikacji z serwerem docelowym, 0 jeśli komunikacja z serwerem poprzez protokół SSL 3.0 nie jest dopuszczalna. |
|
Specyfikacja podsieci podawana w przypadku serwerów dynamicznych, np. 192.168.0.0/24 |
Obiekt/parametr | Opis |
---|---|
server_address |
Adres IP serwera. W przypadku serwerów dynamicznych, pojedynczy obiekt może mieć przypisanych wiele adresów IP. |
|
Adres serwera. |
|
Certyfikat dla danego adresu IP serwera. |
|
Klucz publiczny SSH dla danego adresu IP. |
account |
Obiekt konto zdefiniowany w lokalnej bazie danych. |
|
Nazwa obiektu. |
|
Opis obiektu. |
|
Login konta uprzywilejowanego na monitorowanym serwerze. |
|
Metoda uwierzytelnienia, przyjmuje wartość hasła lub klucz SSH. |
|
Sekret używany w procesie uwierzytelnienia. |
Przykład:
{
"name": "Redmine",
"plugin_version": "1.0.3",
"type": "password changer",
"engine_version": "1.0.0",
"timeout": "300",
"change":
{
"variables":
[
{
"name": "transport_login",
"description": "User name used to login to account.",
"required": true,
"object_type": "account",
"object_property": "login"
},
{
"name": "transport_secret",
"description": "A secret to be used when logging in.",
"required": true,
"object_type": "account",
"object_property": "secret"
},
{
"name": "transport_host",
"description": "Host name or IP address. IPv4 and IPv6 are both supported.",
"required": true,
"object_type": "server_address",
"object_property": "host"
},
{
"name": "account_login",
"description": "User name for which to change password.",
"required": true,
"object_type": "account",
"object_property": "login"
}
]
},
"verify":
{
"variables":
[
{
"name": "transport_login",
"description": "User name used to login to account. This user's password will be verified.",
"required": true,
"object_type": "account",
"object_property": "login"
},
{
"name": "transport_secret",
"description": "A secret that will be verified.",
"required": true,
"object_type": "account",
"object_property": "secret"
},
{
"name": "transport_host",
"description": "Host name or IP address. IPv4 and IPv6 are both supported.",
"required": true,
"object_type": "server_address",
"object_property": "host"
}
]
}
}
Skrypt change¶
Skrypt inicjujący wykonanie właściwego kodu zmieniającego hasła.
Przykład:
#!/bin/sh
CURR_DIR="$(realpath $(dirname "${0}"))"
echo "Script located in '${CURR_DIR}' directory."
export PYTHONPATH="${CURR_DIR}/site-packages"
python3 "${CURR_DIR}/redmine_changer.py" change
Skrypt verify¶
Skrypt inicjujący wykonanie właściwego kodu weryfikującego hasła.
Przykład:
#!/bin/sh
CURR_DIR="$(realpath $(dirname "${0}"))"
echo "Script located in '${CURR_DIR}' directory."
export PYTHONPATH="${CURR_DIR}/site-packages"
python3 "${CURR_DIR}/redmine_changer.py" verify
Kod modyfikujący hasło¶
Informacja
Wszelkie zmienne zadeklarowane w pliku manifest.json
dostępne są poprzez zmienne środowiskowe. Oprócz nich, funkcjonuje zmienna specjalna account_new_secret
, dostępna tylko w skrypcie modyfikującym hasło. Zmienna inicjowana jest wartością wygenerowaną automatycznie przez Fudo PAM.
Przykład odwołania się do zmiennej:
import os
print('New secret: {}'.format(os.environ['account_new_secret']))
Przykład kodu w języku Python zmieniającego hasło do serwisu Redmine za pomocą REST API:
import os
import sys
import requests
MODE_CHANGE = 1
MODE_VERIFY = 2
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
class RedmineChangerError(Exception):
pass
def redmine_get_user_id(server_uri, admin_login, admin_password, user_login):
req = requests.get(
server_uri + '/users.json',
params={'name': user_login},
auth=(admin_login, admin_password),
verify=False,
)
if req.status_code != 200:
raise RedmineChangerError(
'HTTP status code {} from {}.'.format(req.status_code, server_uri)
)
user_list = [x for x in req.json()['users'] if x['login'] == user_login]
if len(user_list) > 1:
raise RedmineChangerError(
'Ambigious answer from {}: Multiple users with "{}" login'.format(
server_uri, user_login
)
)
if len(user_list) < 1:
raise RedmineChangerError(
'Response from {} doesn\'t contain user with login "{}"'.format(
server_uri, user_login
)
)
try:
user_id = user_list[0]['id']
except KeyError:
raise RedmineChangerError(
'Response from {} doesn\'t contain "id".'.format(server_uri)
)
return user_id
def redmine_set_user_password(
server_uri, admin_login, admin_password, user_id, user_password
):
uri = '{}/users/{}.json'.format(server_uri, user_id)
req = requests.put(
uri,
json={'user': {'password': user_password}},
auth=(admin_login, admin_password),
verify=False,
)
if req.status_code != 200:
raise RedmineChangerError(
'HTTP status code {} from {}.'.format(req.status_code, server_uri)
)
# https://redmine.hostonly.vm/users/current.json
def redmine_get_current_user_login(server_uri, admin_login, admin_password):
req = requests.get(
server_uri + '/users/current.json',
auth=(admin_login, admin_password),
verify=False,
)
if req.status_code != 200:
raise RedmineChangerError(
'HTTP status code {} from {}.'.format(req.status_code, server_uri)
)
try:
login = req.json()['user']['login']
except KeyError:
raise RedmineChangerError('Unable to get "user.login".')
return login
def change(
transport_login,
transport_secret,
transport_uri,
account_login,
account_new_secret,
):
try:
user_id = redmine_get_user_id(
transport_uri, transport_login, transport_secret, account_login
)
except RedmineChangerError as err:
print('Error getting user id: {}'.format(err), file=sys.stderr)
return 1
print('User "{}" has id {}.'.format(account_login, user_id))
try:
redmine_set_user_password(
transport_uri,
transport_login,
transport_secret,
user_id,
account_new_secret,
)
except RedmineChangerError as err:
print('Error setting user password: {}'.format(err), file=sys.stderr)
return 1
print('Successfully changed password for user "{}".'.format(account_login))
return 0
def verify(transport_login, transport_secret, transport_uri):
try:
login = redmine_get_current_user_login(
transport_uri, transport_login, transport_secret
)
except RedmineChangerError as err:
print(
'Error getting current user login: {}'.format(err), file=sys.stderr
)
return 1
if login != transport_login:
print(
'Server {} returned wrong login "{}" - expected "{}".'.format(
transport_uri, login, transport_login
),
file=sys.stderr,
)
return 1
print('Successfully logged in as "{}".'.format(transport_login))
return 0
# TODO: There are some improvements that we can implement in future versions of
# plugin to test update procedure:
# - respect TLS: at the moment we assume TLS is on and connect using HTTPS,
# - verify server certificate,
# - optionally, get port of the server.
def main():
if len(sys.argv) != 2:
print('Provide "change" or "verify" as plugin mode', file=sys.stderr)
sys.exit(1)
if sys.argv[1] == 'change':
mode = MODE_CHANGE
elif sys.argv[1] == 'verify':
mode = MODE_VERIFY
else:
print('Incorrect plugin mode: "{}".'.format(sys.argv[1]))
sys.exit(1)
transport_login = os.environ['transport_login']
transport_secret = os.environ['transport_secret']
transport_uri = 'https://' + os.environ['transport_host']
if mode == MODE_CHANGE:
account_login = os.environ['account_login']
account_new_secret = os.environ['account_new_secret']
result = 1
if mode == MODE_CHANGE:
result = change(
transport_login,
transport_secret,
transport_uri,
account_login,
account_new_secret,
)
else:
result = verify(transport_login, transport_secret, transport_uri)
sys.exit(result)
if __name__ == '__main__':
main()
Informacja
Prawidłowo wykonany kod powinien na wyjściu zwrócić wartość 0. Każda inna wartość będzie zinterpretowana jako zmiana/weryfikacja wykonana niepomyślnie.
Tematy pokrewne: