migrate to secret manager, closes #45

This commit is contained in:
Andy Pack 2022-11-29 21:13:26 +00:00
parent de23eb0065
commit c7fe8fada5
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
8 changed files with 60 additions and 31 deletions

View File

@ -4,6 +4,7 @@ from music.model.user import User
from music.model.config import Config
from music.auth.jwt_keys import generate_key
from music.api.decorators import no_cache
from music.cloud import SPOT_CLIENT_URI, SPOT_SECRET_URI
from urllib.parse import urlencode, urlunparse
import datetime
@ -11,11 +12,14 @@ from datetime import timedelta
from numbers import Number
import logging
from base64 import b64encode
from google.cloud import secretmanager
import requests
blueprint = Blueprint('authapi', __name__)
logger = logging.getLogger(__name__)
secret_client = secretmanager.SecretManagerServiceClient()
@blueprint.route('/login', methods=['GET', 'POST'])
@ -194,10 +198,10 @@ def auth():
if 'username' in session:
config = Config.collection.get("config/music-tools")
spot_client = secret_client.access_secret_version(request={"name": SPOT_CLIENT_URI})
params = urlencode(
{
'client_id': config.spotify_client_id,
'client_id': spot_client.payload.data.decode("UTF-8"),
'response_type': 'code',
'scope': 'playlist-modify-public playlist-modify-private playlist-read-private '
'user-read-playback-state user-modify-playback-state user-library-read',
@ -221,10 +225,11 @@ def token():
flash('authorization failed')
return redirect('app_route')
else:
config = Config.collection.get("config/music-tools")
spot_client = secret_client.access_secret_version(request={"name": SPOT_CLIENT_URI})
spot_secret = secret_client.access_secret_version(request={"name": SPOT_SECRET_URI})
idsecret = b64encode(
bytes(config.spotify_client_id + ':' + config.spotify_client_secret, "utf-8")
bytes(spot_client.payload.data.decode("UTF-8") + ':' + spot_secret.payload.data.decode("UTF-8"), "utf-8")
).decode("ascii")
headers = {'Authorization': 'Basic %s' % idsecret}

View File

@ -1,17 +1,15 @@
from datetime import timedelta, datetime, timezone
import jwt
from music.cloud import JWT_SECRET_URI
from music.model.user import User
from music.model.config import Config
from google.cloud import secretmanager
secret_client = secretmanager.SecretManagerServiceClient()
def get_jwt_secret_key() -> str:
config = Config.collection.get("config/music-tools")
if config.jwt_secret_key is None or len(config.jwt_secret_key) == 0:
raise KeyError("no jwt secret key found")
return config.jwt_secret_key
return secret_client.access_secret_version(request={"name": JWT_SECRET_URI}).payload.data.decode("UTF-8")
def generate_key(user: User, timeout: datetime | timedelta = timedelta(minutes=60)) -> str:

View File

@ -10,6 +10,12 @@ from .tasks import run_user_playlist_task
logger = logging.getLogger(__name__)
SPOT_CLIENT_URI = "projects/sarsooxyz/secrets/spotify-client/versions/latest"
SPOT_SECRET_URI = "projects/sarsooxyz/secrets/spotify-secret/versions/latest"
LASTFM_CLIENT_URI = "projects/sarsooxyz/secrets/lastfm-client/versions/latest"
JWT_SECRET_URI = "projects/sarsooxyz/secrets/jwt-secret/versions/latest"
COOKIE_SECRET_URI = "projects/sarsooxyz/secrets/cookie-secret/versions/latest"
def queue_run_user_playlist(username: str, playlist_name: str):
config = Config.collection.get("config/music-tools")

View File

@ -7,9 +7,13 @@ from spotframework.net.network import Network as SpotifyNetwork, SpotifyNetworkE
from spotframework.net.user import NetworkUser
from fmframework.net.network import Network as FmNetwork
from music.model.user import User
from music.model.config import Config
from music.cloud import SPOT_CLIENT_URI, SPOT_SECRET_URI, LASTFM_CLIENT_URI
from google.cloud import secretmanager
logger = logging.getLogger(__name__)
secret_client = secretmanager.SecretManagerServiceClient()
def refresh_token_database_callback(user: User) -> None:
@ -50,10 +54,11 @@ def get_authed_spotify_network(user: User) -> Optional[SpotifyNetwork]:
if user is not None:
if user.spotify_linked:
config = Config.collection.get("config/music-tools")
spot_client = secret_client.access_secret_version(request={"name": SPOT_CLIENT_URI})
spot_secret = secret_client.access_secret_version(request={"name": SPOT_SECRET_URI})
user_obj = DatabaseUser(client_id=config.spotify_client_id,
client_secret=config.spotify_client_secret,
user_obj = DatabaseUser(client_id=spot_client.payload.data.decode("UTF-8"),
client_secret=spot_secret.payload.data.decode("UTF-8"),
refresh_token=user.refresh_token,
user_id=user.username,
access_token=user.access_token)
@ -92,8 +97,9 @@ def get_authed_lastfm_network(user: User) -> Optional[FmNetwork]:
if user is not None:
if user.lastfm_username:
config = Config.collection.get("config/music-tools")
return FmNetwork(username=user.lastfm_username, api_key=config.last_fm_client_id)
lastfm_client = secret_client.access_secret_version(request={"name": LASTFM_CLIENT_URI})
return FmNetwork(username=user.lastfm_username, api_key=lastfm_client.payload.data.decode("UTF-8"))
else:
logger.error(f'{user.username} has no last.fm username')
else:

View File

@ -11,14 +11,8 @@ class Config(Model):
"""Set correct path in Firestore
"""
spotify_client_id = TextField()
spotify_client_secret = TextField()
last_fm_client_id = TextField()
playlist_cloud_operating_mode = TextField() # task, function
"""Determines whether playlist and tag update operations are done by Cloud Tasks or Functions
"""
secret_key = TextField()
jwt_secret_key = TextField()
jwt_max_length = NumberField()
jwt_default_length = NumberField()

View File

@ -1,4 +1,5 @@
from flask import Flask, render_template, redirect, session, flash, url_for
from google.cloud import secretmanager
import logging
import os
@ -6,9 +7,10 @@ import os
from music.auth import auth_blueprint
from music.api import api_blueprint, player_blueprint, fm_blueprint, \
spotfm_blueprint, spotify_blueprint, admin_blueprint, tag_blueprint
from music.model.config import Config
from music.cloud import COOKIE_SECRET_URI
logger = logging.getLogger(__name__)
secret_client = secretmanager.SecretManagerServiceClient()
def create_app():
@ -20,11 +22,7 @@ def create_app():
app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates")
config = Config.collection.get("config/music-tools")
if config is not None:
app.secret_key = config.secret_key
else:
logger.error('no config returned, skipping secret key')
app.secret_key = secret_client.access_secret_version(request={"name": COOKIE_SECRET_URI}).payload.data.decode("UTF-8")
app.register_blueprint(auth_blueprint, url_prefix='/auth')
app.register_blueprint(api_blueprint, url_prefix='/api')

23
poetry.lock generated
View File

@ -291,6 +291,23 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4
[package.extras]
libcst = ["libcst (>=0.3.10)"]
[[package]]
name = "google-cloud-secret-manager"
version = "2.12.6"
description = "Secret Manager API API client library"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
google-api-core = {version = ">=1.32.0,<2.0.0 || >=2.8.0,<3.0.0dev", extras = ["grpc"]}
grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev"
proto-plus = ">=1.22.0,<2.0.0dev"
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
[package.extras]
libcst = ["libcst (>=0.2.5)"]
[[package]]
name = "google-cloud-tasks"
version = "2.10.4"
@ -835,7 +852,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata]
lock-version = "1.1"
python-versions = "~3.10"
content-hash = "fef135789689e4ab5a67d8e37ce0da3ca5b772a500a3ce9ac69ac5b2876b5175"
content-hash = "ab592da5f6b73bd51841928689b332bdd994e188398b85a1d5e48cc402c89dac"
[metadata.files]
alabaster = [
@ -923,6 +940,10 @@ google-cloud-pubsub = [
{file = "google-cloud-pubsub-2.13.10.tar.gz", hash = "sha256:3e746a1a97c96bdc63fc5499529ed7262f1464f6de8c8bae6238c12200919ac0"},
{file = "google_cloud_pubsub-2.13.10-py2.py3-none-any.whl", hash = "sha256:1a29795265ddb2eff23b4e4c2c4594a56f5a861827fe1cc72c53de42a3a13def"},
]
google-cloud-secret-manager = [
{file = "google-cloud-secret-manager-2.12.6.tar.gz", hash = "sha256:7aaba2997f43f8c3b35903efc54c389a802f39853ba81e81e1190f093420ecf6"},
{file = "google_cloud_secret_manager-2.12.6-py2.py3-none-any.whl", hash = "sha256:585e2ccd05038526b39eeabfe6932a7398341a76770ee0dd797c8581313e5212"},
]
google-cloud-tasks = [
{file = "google-cloud-tasks-2.10.4.tar.gz", hash = "sha256:650f4f43be725841103727ea3bc13227ae9ebaa2f99dadaad53888831cfc1d70"},
{file = "google_cloud_tasks-2.10.4-py2.py3-none-any.whl", hash = "sha256:d5bc498eff25b145bd0a2d67ee466814dc9baf259a5ee8c4187aef267f4d83d4"},

View File

@ -19,6 +19,7 @@ Flask = "^2.1.3"
google-cloud-firestore = "<=2.3.4"
google-cloud-logging = "^3.2.1"
google-cloud-pubsub = "^2.13.4"
google-cloud-secret-manager = "^2.12.6"
google-cloud-tasks = "^2.10.0"
requests = "^2.28.1"
PyJWT = "^2.4.0"