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

View File

@ -1,17 +1,15 @@
from datetime import timedelta, datetime, timezone from datetime import timedelta, datetime, timezone
import jwt import jwt
from music.cloud import JWT_SECRET_URI
from music.model.user import User 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: def get_jwt_secret_key() -> str:
return secret_client.access_secret_version(request={"name": JWT_SECRET_URI}).payload.data.decode("UTF-8")
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
def generate_key(user: User, timeout: datetime | timedelta = timedelta(minutes=60)) -> str: 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__) 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): def queue_run_user_playlist(username: str, playlist_name: str):
config = Config.collection.get("config/music-tools") 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 spotframework.net.user import NetworkUser
from fmframework.net.network import Network as FmNetwork from fmframework.net.network import Network as FmNetwork
from music.model.user import User 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__) logger = logging.getLogger(__name__)
secret_client = secretmanager.SecretManagerServiceClient()
def refresh_token_database_callback(user: User) -> None: 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 is not None:
if user.spotify_linked: 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, user_obj = DatabaseUser(client_id=spot_client.payload.data.decode("UTF-8"),
client_secret=config.spotify_client_secret, client_secret=spot_secret.payload.data.decode("UTF-8"),
refresh_token=user.refresh_token, refresh_token=user.refresh_token,
user_id=user.username, user_id=user.username,
access_token=user.access_token) 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 is not None:
if user.lastfm_username: if user.lastfm_username:
config = Config.collection.get("config/music-tools") lastfm_client = secret_client.access_secret_version(request={"name": LASTFM_CLIENT_URI})
return FmNetwork(username=user.lastfm_username, api_key=config.last_fm_client_id)
return FmNetwork(username=user.lastfm_username, api_key=lastfm_client.payload.data.decode("UTF-8"))
else: else:
logger.error(f'{user.username} has no last.fm username') logger.error(f'{user.username} has no last.fm username')
else: else:

View File

@ -11,14 +11,8 @@ class Config(Model):
"""Set correct path in Firestore """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 playlist_cloud_operating_mode = TextField() # task, function
"""Determines whether playlist and tag update operations are done by Cloud Tasks or Functions """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_max_length = NumberField()
jwt_default_length = NumberField() jwt_default_length = NumberField()

View File

@ -1,4 +1,5 @@
from flask import Flask, render_template, redirect, session, flash, url_for from flask import Flask, render_template, redirect, session, flash, url_for
from google.cloud import secretmanager
import logging import logging
import os import os
@ -6,9 +7,10 @@ import os
from music.auth import auth_blueprint from music.auth import auth_blueprint
from music.api import api_blueprint, player_blueprint, fm_blueprint, \ from music.api import api_blueprint, player_blueprint, fm_blueprint, \
spotfm_blueprint, spotify_blueprint, admin_blueprint, tag_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__) logger = logging.getLogger(__name__)
secret_client = secretmanager.SecretManagerServiceClient()
def create_app(): 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") app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates")
config = Config.collection.get("config/music-tools") app.secret_key = secret_client.access_secret_version(request={"name": COOKIE_SECRET_URI}).payload.data.decode("UTF-8")
if config is not None:
app.secret_key = config.secret_key
else:
logger.error('no config returned, skipping secret key')
app.register_blueprint(auth_blueprint, url_prefix='/auth') app.register_blueprint(auth_blueprint, url_prefix='/auth')
app.register_blueprint(api_blueprint, url_prefix='/api') 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] [package.extras]
libcst = ["libcst (>=0.3.10)"] 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]] [[package]]
name = "google-cloud-tasks" name = "google-cloud-tasks"
version = "2.10.4" version = "2.10.4"
@ -835,7 +852,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "~3.10" python-versions = "~3.10"
content-hash = "fef135789689e4ab5a67d8e37ce0da3ca5b772a500a3ce9ac69ac5b2876b5175" content-hash = "ab592da5f6b73bd51841928689b332bdd994e188398b85a1d5e48cc402c89dac"
[metadata.files] [metadata.files]
alabaster = [ 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.tar.gz", hash = "sha256:3e746a1a97c96bdc63fc5499529ed7262f1464f6de8c8bae6238c12200919ac0"},
{file = "google_cloud_pubsub-2.13.10-py2.py3-none-any.whl", hash = "sha256:1a29795265ddb2eff23b4e4c2c4594a56f5a861827fe1cc72c53de42a3a13def"}, {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 = [ google-cloud-tasks = [
{file = "google-cloud-tasks-2.10.4.tar.gz", hash = "sha256:650f4f43be725841103727ea3bc13227ae9ebaa2f99dadaad53888831cfc1d70"}, {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"}, {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-firestore = "<=2.3.4"
google-cloud-logging = "^3.2.1" google-cloud-logging = "^3.2.1"
google-cloud-pubsub = "^2.13.4" google-cloud-pubsub = "^2.13.4"
google-cloud-secret-manager = "^2.12.6"
google-cloud-tasks = "^2.10.0" google-cloud-tasks = "^2.10.0"
requests = "^2.28.1" requests = "^2.28.1"
PyJWT = "^2.4.0" PyJWT = "^2.4.0"