Conservation des secrets sous Linux: authentification JWT dans une application CLI Python

JSON Web Token est un standard ouvert pour la création de jetons d'accès basés sur le format JSON. Généralement utilisé pour transmettre les données d'authentification dans les applications client-serveur. Wikipédia

Lorsqu'il s'agit de stocker des données sensibles dans un navigateur, il vous suffit d'utiliser l'une des deux options disponibles: cookies ou stockage local. Ici, tout le monde choisit de goûter. Cependant, j'ai dédié cet article à Secret Service, un service qui fonctionne sur D-Bus et est conçu pour stocker des «secrets» sous Linux.

Le service dispose d'une API que GNOME Keyring utilise pour stocker les secrets d'application.

Pourquoi Secret Service

Le fait est que je ne recevais pas le jeton dans le navigateur. J'écrivais l'authentification client pour une application console similaire à celle utilisée dans git.

La question sur la méthode de stockage des détails s'est posée immédiatement, car je ne voulais pas forcer les utilisateurs à se connecter au prochain lancement de mon application.

Au début, il y avait une option pour stocker le jeton dans un fichier crypté, mais il a disparu tout de suite parce que j'ai deviné que mes fonctions de cryptage et de décryptage seraient un vélo.

Ensuite, j'ai pensé à la façon dont Linux garde des secrets, et il s'est avéré que des mécanismes similaires sont implémentés dans d'autres systèmes d'exploitation.

En conséquence, le mot de passe du compte utilisateur Linux servira de clé d'accès au jeton.

L'architecture des services secrets en un coup d'œil

La structure de données principale des services secrets est une collection d'éléments avec des attributs et un secret.

Collection

Il s'agit d'une collection de toutes sortes de données d'authentification. Le système utilise la collection par défaut sous l'alias "default". Toutes les applications utilisateur y sont écrites. Seahorse nous le montrera.

Comme vous pouvez le voir, Google Chrome et VSCode sont enregistrés dans mon stockage. Ma candidature sera également enregistrée ici.

Chacun de ces enregistrements est appelé un élément.

Élément

La partie de la collection qui stocke des attributs et un secret.

Les attributs

Une paire de la clé de formulaire, valeur, qui contient le nom de l'application et sert à identifier l'élément.

Secret

, , , .

Secret Service

, flow chart.

  • « ?» — .

  • « » — .

  • « » — .

  • « API» — .

  • « » — .

  • « » — .

Python

Click Framework CLI .

import click

, , . . , .

@click.group()
def cli():
    pass

.

:

$ app login
Email:
Password:

:

$ app login
Logged in!

login

@cli.command(help="Login into your account.")
@click.option(
    '--email',
    prompt=True,
    help='Registered email address.')
@click.option(
    '--password',
    prompt=True,
    hide_input=True,
    help='Password provided at registration.'
    )
def login(email, password):
        pass

if __name__ == '__main__':
    cli()

login, , email password.

@cli.command , @click.option .

, hide_input .

prompt , lick Framework , .

True False , :

  • True Click Framework . . , WEB API Secret Service, Secret Service API;

  • False Click Framework . , , Secret Service.

, prompt_desicion. Secret Service. , Secret Service API .

. , , Click Framework.

, , Click Framework, .

app Click Framework. auth, Auth .

.
├── auth.py
└── app.py

prompt_desicion auth Auth auth.

@cli.command(help="Login into your account.")
@click.option(
    '--email',
    prompt=auth.prompt_desicion,
    help='Registered email address.')
@click.option(
    '--password',
    prompt=auth.prompt_desicion,
    hide_input=True,
    help='Password provided at registration.'
    )
def login(email, password):
        pass

if __name__ == '__main__':
    cli()

Python SecreteStorage, Secret Service API.

, Secret Service.

Secret Service API WEB API, prompt_desicion .

  • requests — HTTP WEB API.

  • secretstorage — Secret Service API.

  • json — .

import requests
import secretstorage
import json

Secrete Storage

class Auth:
    def __init__(self, email=None, password=None):
        # ,     
        # Secret Service
        self._attributes = {'application': 'MyApp'}
        #   Dbus
        self._connection = secretstorage.dbus_init()
        #   -
        self._collection = secretstorage.collection.get_default_collection(
            self._connection
            )
        #       
        self._items = self._collection.search_items(self._attributes)
        #   
        self._stored_secret = self.get_stored_secret()

.

Secret Service self._attributes.

«_»

, . , . , . , , . , .

, . () SecretStorage () . , , self._items .

get_stored_secret, .

class Auth:
    def get_stored_secret(self):
        for item in self._items:
            if item:
                return json.loads(item.get_secret())

Item secretstorage, get_secret, .

. .

.

True False — ,

, , : « — False».

class Auth:        
    def __init__(self, email=None, password=None):
        # ,     
        self.prompt_desicion = False

; , .

. , .

class Auth:        
    def __init__(self, email=None, password=None):
        # ,     
        #   
        if self._stored_secret:
            #      token
            self.token = self._stored_secret['token']
        #       
        elif email and password:
            #    WEB API
            self.token = self.get_token(email, password)
            #    
            self._valid_secret = {'token': self.token}
            #    Secret Service
            self.set_stored_secret()
        else:
            #     Secret Storage,     
            # 
            self.prompt_desicion = True

- .

  1. Secret Storage API.

  2. , .

  3. Secret Storage.

  1. Secret Storage API.

  2. , Secret Storage.

  3. , .

  4. , WEB API.

  5. , Secret Storage.

Secret Storage WEB API.

WEB API

class Auth:        
    def get_token(self, email: str, password: str) -> str:
        try:
            response = requests.post(
                API_URL,
                data= {
                    'email': email,
                    'passwd': password
                    })
            data = response.json()
        except requests.exceptions.ConnectionError:
            raise requests.exceptions.ConnectionError()
        if response.status_code != 200:
            raise requests.exceptions.HTTPError(data['msg'])
        return data['data']['token']

API_URL API. , . , POST «email» «passwd».

API , API .

API «msg» . try .

«data».

Secret Storage

class Auth:        
    def set_stored_secret(self):
        self._collection.create_item(
            'MyApp',
            self._attributes,
            bytes((json.dumps(self._valid_secret)), 'utf-8')
            )

create_item , .

lick Framework

auth.

from auth import Auth

Secret Storage.

auth = Auth()

.

@cli.command(help="Login into VPN Manager account.")
@click.option(
    '--email',
    prompt=auth.prompt_desicion,
    help='Registered email address.')
@click.option(
    '--password',
    prompt=auth.prompt_desicion,
    hide_input=True,
    help='Password provided at registration.'
    )

login.

def login(email, password):
    global auth
    try:
      	#         
        if auth.prompt_desicion:
          	#        Secret Storage
            auth = Auth(email, password)
    except Exception:
        return click.echo('No API connection')
		#     ,   .
    click.echo(auth.token)

Click Framework génère automatiquement de l'aide. Pour ce faire, il a besoin des lignes que j'ai spécifiées dans le paramètre de helpses décorateurs.

$ python app.py 
Usage: app.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  login  Login into your account.

Aide sur la commande de connexion

$ python app.py login --help
Usage: app.py login [OPTIONS]

  Login into your account

Options:
  --email TEXT     Registered email address
  --password TEXT  Password provided at registration
  --help           Show this message and exit.

Vérifier

Après avoir démarré l'application avec la commande, python app.py loginelle vous demandera un mail et un mot de passe. Si ces données sont correctes, l'élément correspondant apparaîtra dans le service secret.

Il stocke en fait le jeton.

Lorsque vous le redémarrez, l'application ne vous demandera pas de détails, mais téléchargera le jeton depuis les services secrets.

Liens




All Articles