5
votes

Problèmes d'autorisations d'importation Cloud SQL pour le bucket Cloud Storage

J'écris une fonction cloud à:

Remarque: Je souhaite que ce code s'exécute de manière autonome chaque nuit pour copier une base de données de production dans un environnement de préparation. Je prévois donc de le déclencher à l'aide de Cloud Scheduler.
Si vous avez une solution meilleure / plus simple pour extraire cela dans GCP, je suis à l'écoute :)

Voici mon code (la fonction réelle est clone_db au bas du fichier ):

Error: function crashed. Details:
<HttpError 403 when requesting https://www.googleapis.com/sql/v1beta4/projects/<project_id>/instances/<instance_id>/import?alt=json returned "The service account does not have the required permissions for the bucket.">

Les choses fonctionnent parfaitement bien jusqu'à ce que j'essaye d ' importer les données exportées dans mon instance cible.

Lorsqu'elle appelle import_ , la fonction échoue avec l'erreur suivante:

from os import getenv
from datetime import datetime
from time import sleep

from googleapiclient import discovery
from googleapiclient.errors import HttpError
from oauth2client.client import GoogleCredentials
from google.cloud import storage

GS_BUCKET = getenv("GS_BUCKET")
GS_FOLDER = "sql-exports"
GS_EXPORT_PATH = f"gs://{GS_BUCKET}/{GS_FOLDER}"


def __sql_file_name(db: str, timestamp: datetime):
    return f"{db}-{timestamp.strftime('%Y-%m-%d')}.sql.gz"


def __sql_file_uri(db: str, timestamp: datetime):
    return f"{GS_EXPORT_PATH}/{__sql_file_name(db, timestamp)}"


def __export_source_db(service, project: str, timestamp: datetime, instance: str, db: str):
    context = {
        "exportContext": {
            "kind": "sql#exportContext",
            "fileType": "SQL",
            "uri": __sql_file_uri(db, timestamp),
            "databases": [db],
        }
    }

    return service.instances().export(project=project, instance=instance, body=context).execute()


def __import_target_db(service, project: str, timestamp: datetime, instance: str, db: str):
    context = {
        "importContext": {
            "kind": "sql#importContext",
            "fileType": "SQL",
            "uri": __sql_file_uri(db, timestamp),
            "database": db,
        }
    }

    return service.instances().import_(project=project, instance=instance, body=context).execute()


def __drop_db(service, project: str, instance: str, db: str):
    try:
        return service.databases().delete(project=project, instance=instance, database=db).execute()
    except HttpError as e:
        if e.resp.status == 404:
            return {"status": "DONE"}
        else:
            raise e


def __create_db(service, project: str, instance: str, db: str):
    database = {
        "name": db,
        "project": project,
        "instance": instance,
    }

    return service.databases().insert(project=project, instance=instance, body=database).execute()


def __update_export_permissions(file_name: str):
    client = storage.Client()
    file = client.get_bucket(GS_BUCKET).get_blob(f"{GS_FOLDER}/{file_name}")
    file.acl.user(getenv("TARGET_DB_SERVICE_ACCOUNT")).grant_read()
    file.acl.save()


def __delete_sql_file(file_name: str):
    client = storage.Client()
    bucket = client.get_bucket(GS_BUCKET)
    bucket.delete_blob(f"{GS_FOLDER}/{file_name}")


def __wait_for(operation_type, operation, service, project):
    if operation["status"] in ("PENDING", "RUNNING", "UNKNOWN"):
        print(f"{operation_type} operation in {operation['status']} status. Waiting for completion...")

        while operation['status'] != "DONE":
            sleep(1)
            operation = service.operations().get(project=project, operation=operation['name']).execute()

    print(f"{operation_type} operation completed!")


def clone_db(_):
    credentials = GoogleCredentials.get_application_default()
    service = discovery.build('sqladmin', 'v1beta4', credentials=credentials)

    # Project ID of the project that contains the instance to be exported.
    project = getenv('PROJECT_ID')

    # Cloud SQL instance ID. This does not include the project ID.
    source = {
        "instance": getenv("SOURCE_INSTANCE_ID"),
        "db": getenv("SOURCE_DB_NAME")
    }

    timestamp = datetime.utcnow()

    print(f"Exporting database {source['instance']}:{source['db']} to Cloud Storage...")
    operation = __export_source_db(service, project, timestamp, **source)

    __wait_for("Export", operation, service, project)

    print("Updating exported file permissions...")
    __update_export_permissions(__sql_file_name(source["db"], timestamp))
    print("Done.")

    target = {
        "instance": getenv("TARGET_INSTANCE_ID"),
        "db": getenv("TARGET_DB_NAME")
    }

    print(f"Dropping target database {target['instance']}:{target['db']}")
    operation = __drop_db(service, project, **target)
    __wait_for("Drop", operation, service, project)

    print(f"Creating database {target['instance']}:{target['db']}...")
    operation = __create_db(service, project, **target)
    __wait_for("Creation", operation, service, project)

    print(f"Importing data into {target['instance']}:{target['db']}...")
    operation = __import_target_db(service, project, timestamp, **target)
    __wait_for("Import", operation, service, project)

    print("Deleting exported SQL file")
    __delete_sql_file(__sql_file_name(source["db"], timestamp))
    print("Done.")

J'ai lu cette erreur dans de nombreuses autres questions et réponses ici et sur le Web , mais je n'arrive pas à comprendre comment faire fonctionner les choses.
Voici ce que j'ai fait:

  • La fonction Cloud est exécutée en tant que "compte de service par défaut Compute Engine", qui a le rôle Éditeur de projet défini dans IAM
  • Le compte de service de l'instance Cloud SQL cible est ajouté aux autorisations du bucket en tant qu ' Storage Object Admin . J'ai essayé diverses autres combinaisons de rôles (lecteur / propriétaire hérité, visionneuse d'objets de stockage, ...) en vain
  • Comme vous pouvez le voir dans le code de la fonction, j'accorde spécifiquement un accès en lecture au compte de service de l'instance cible pour le fichier exporté, et cela se reflète correctement sur les autorisations de l'objet dans le stockage cloud:

 Permissions d'objet GCS

  • J'ai essayé de désactiver les autorisations au niveau de l'objet pour ce compartiment et je me suis assuré que les autorisations de mon premier point ci-dessus étaient correctement définies, mais cela n'a pas fonctionné non plus

Fait intéressant, lorsque j'essaie d'importer manuellement le même fichier sur la même instance à partir de la console GCP Cloud SQL, les choses fonctionnent parfaitement bien.
Une fois que c'est fait, je peux voir que les autorisations de mon fichier exporté ont été mises à jour pour inclure le compte de service de l'instance en tant que Reader , tout comme je l'ai fait dans mon code à la fin pour essayer de reproduire le comportement.

Que me manque-t-il donc ici?
Quelles autorisations dois-je définir, pour quel compte de service, pour que cela fonctionne?


0 commentaires

3 Réponses :


0
votes

L'instance CloudSQL s'exécute sous un compte de service Google qui ne fait pas partie de votre projet.

Vous devez trouver le compte de service de votre instance - Cloud SQL-> nom du cluster -> Compte de service

 entrez la description de l'image ici

Ensuite, vous prenez le compte de service ci-dessus et lui donnez l'autorisation d'écriture / lecture pour le bucket concerné


1 commentaires

C'est ce que je voulais dire lorsque j'ai dit que j'avais essayé ce qui suit: "Le compte de service de l'instance Cloud SQL cible est ajouté aux autorisations du bucket en tant qu'administrateur d'objets de stockage. J'ai essayé diverses autres combinaisons de rôles (lecteur / propriétaire hérité, visionneuse d'objets de stockage, ...) en vain "



1
votes

J'ai eu le même problème et j'ai essayé beaucoup de choses différentes. Même après avoir donné les droits de propriétaire du compte de service DB sur le projet, le bucket et les fichiers SQL, cela ne fonctionnait pas lors de l'importation / exportation depuis / vers d'autres fichiers fonctionnait toujours.

J'ai donc fini par renommer mon fichier d'importation et, étonnamment, cela a fonctionné (l'ancien nom de fichier était assez long et contenait des traits de soulignement comme dans votre exemple). Mais je ne trouve rien dans la documentation sur ces limitations de dénomination et à ce stade, je ne peux même pas dire si ce problème est lié au nom de fichier ou à l'utilisation de traits de soulignement. Mais cela vaut peut-être la peine d'essayer.


1 commentaires

Mec, c'est incroyable: D Je suis sur ce sujet depuis un mois avec le support de Google, je suis remonté aux ingénieurs de l'équipe Cloud Functions et nous n'avons pas pu trouver le problème. Le message d'erreur est clairement trompeur et je suppose qu'il y a un joli bogue à corriger; Je vais faire un suivi avec eux avec cette solution de contournement; Merci!!



0
votes

Le problème vient de votre code et non de Cloud SQL.

Lorsque vous appelez la fonction _import_target_db , vous recherchez un fichier qui n'existe pas dans votre bucket Cloud Storage.

Entrer dans les détails:

Vous avez exporté la base de données vers votre bucket avec le nom:

gs://yourBucket/sql-exports/exportedDatabaseName-yyyy-mm-dd.sql.gz

Cependant, lorsque vous essayez de l'importer, la fonction d'importation recherche un fichier nommé:

gs: //yourBucket/sql-exports/importDatabaseName-yyyy-mm-dd.sql.gz

Ce fichier n'existe pas dans votre bucket et pour des raisons de sécurité, une erreur 403 Forbidden est renvoyée.


1 commentaires

Oui, vous avez absolument raison! Nous l'avons repéré par la suite et j'ai oublié de mettre à jour la question ... l'autorisation d'accès m'a jeté et je n'ai pas remis en question la validité de mon code quand cela s'est produit, ce qui en fait un cas compliqué pour rien 😅 La réponse de Google à «pourrait-il y avoir un meilleur message d'erreur »est que pour des raisons de sécurité, il vaut mieux ne pas divulguer les informations de« fichier non trouvé »par rapport à« aucune permission d'y accéder », ce qui est logique. En tout cas merci de regarder dedans, bonne prise!