Menjaga Rahasia di Linux: Autentikasi JWT dalam Aplikasi Python CLI

JSON Web Token adalah standar terbuka untuk membuat token akses berdasarkan format JSON. Biasanya digunakan untuk meneruskan data otentikasi dalam aplikasi server-klien. Wikipedia

Saat ingin menyimpan data sensitif di browser, Anda hanya perlu menggunakan salah satu dari dua opsi yang tersedia: cookie atau penyimpanan lokal. Di sini semua orang memilih untuk mencicipi. Namun, saya mendedikasikan artikel ini untuk Secret Service, sebuah layanan yang berjalan di atas D-Bus dan dirancang untuk menyimpan "rahasia" di Linux.

Layanan ini memiliki API yang digunakan GNOME Keyring untuk menyimpan rahasia aplikasi.

Mengapa Secret Service

Masalahnya, saya tidak menerima token di browser. Saya sedang menulis otentikasi klien untuk aplikasi konsol yang mirip dengan yang digunakan di git.

Pertanyaan tentang metode penyimpanan detail segera muncul, karena saya tidak ingin memaksa pengguna untuk masuk saat aplikasi saya diluncurkan lagi.

Pada awalnya ada opsi untuk menyimpan token dalam file terenkripsi, tetapi token itu langsung menghilang karena saya menduga bahwa fungsi enkripsi dan dekripsi saya adalah sepeda.

Kemudian saya berpikir tentang bagaimana Linux menyimpan rahasia, dan ternyata mekanisme serupa diterapkan di sistem operasi lain.

Akibatnya, kata sandi akun pengguna Linux akan berfungsi sebagai kunci akses ke token.

Sekilas tentang arsitektur Secret Service

Struktur data utama Secret Service adalah kumpulan elemen dengan atribut dan rahasia.

Koleksi

Ini adalah kumpulan semua jenis data otentikasi. Sistem menggunakan koleksi default di bawah alias "default". Semua aplikasi pengguna ditulis untuk itu. Kuda laut akan menunjukkannya pada kita.

Seperti yang Anda lihat, Google Chrome dan VSCode disimpan di penyimpanan saya. Aplikasi saya juga akan disimpan di sini.

Setiap catatan tersebut disebut item.

Elemen

Bagian dari koleksi yang menyimpan atribut dan rahasia.

Atribut

Sepasang kunci formulir, nilai, yang berisi nama aplikasi dan berfungsi untuk mengidentifikasi elemen.

Rahasia

, , , .

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)

Klik Framework menghasilkan bantuan secara otomatis. Untuk melakukan ini, dia membutuhkan garis yang saya tentukan di parameter helpdekoratornya.

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

Options:
  --help  Show this message and exit.

Commands:
  login  Login into your account.

Bantuan perintah login

$ 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.

Memeriksa

Setelah memulai aplikasi dengan perintah, python app.py loginitu akan meminta surat dan kata sandi. Jika data ini benar, maka elemen yang sesuai akan muncul di Secrete Service.

Itu sebenarnya menyimpan token.

Ketika Anda memulai ulang aplikasi, itu tidak akan meminta detail, tetapi akan mengunduh token dari Secret Service.

Tautan




All Articles