Authentifizierung für Server-zu-Server-Produktionsanwendungen einrichten

In diesem Leitfaden wird gezeigt, wie Sie die Authentifizierung und Autorisierung für Server-zu-Server-Produktionsanwendungen einrichten. Authentifizierung ist der Vorgang, bei dem die Identität eines Clients ermittelt wird. Bei der Autorisierung wird festgelegt, welche Berechtigungen ein authentifizierter Client für eine bestimmte Ressource hat. Mit der Authentifizierung wird also Ihre Identität ermittelt und mit der Autorisierung wird bestimmt, was Sie tun können. Weitere Informationen zu unterstützten Authentifizierungsmethoden und deren Auswahl finden Sie unter Authentifizierung.

Google identifiziert mit den Anmeldedaten Ihre Anwendung für Kontingente und für die Abrechnung. Außerdem wird der Zugriff auf GCP APIs, Ressourcen und Features anhand Ihrer Anmeldedaten autorisiert.

Anmeldedaten für Ihre Anwendung bereitstellen

Anmeldedaten automatisch finden

GCP-Clientbibliotheken gehen bei der Suche nach den Anmeldedaten Ihrer Anwendung anhand einer Strategie vor, die sich Standardanmeldedaten für Anwendungen nennt. Wenn Sie in Ihrem Code eine Clientbibliothek verwenden, werden Ihre Anmeldedaten gemäß dieser Strategie in folgender Reihenfolge geprüft:

  1. Zuerst wird geprüft, ob die Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS festgelegt ist. Ist dies der Fall, wird die Dienstkontodatei, auf die die Variable verweist, für die Anmeldedaten verwendet. Im nächsten Abschnitt wird gezeigt, wie Sie die Umgebungsvariable festlegen.

  2. Wenn die Umgebungsvariable nicht festgelegt ist, verwendet ADC das Standarddienstkonto, das von Compute Engine, Kubernetes Engine, Cloud Run, App Engine und Cloud Functions bereitgestellt wird, für die Anwendungen, die für diese Dienste ausgeführt werden.

  3. Wenn die Anmeldedaten weder im ersten noch im zweiten Schritt ermittelt werden können, wird eine Fehlermeldung ausgegeben.

Das folgende Codebeispiel veranschaulicht dieses Vorgehen. In diesem Beispiel werden die Anmeldedaten für die Anwendung nicht explizit angegeben. Sie können aber im Rahmen dieses Vorgehens von ADC implizit ermittelt werden, sofern die Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS festgelegt ist oder die Anwendung in Compute Engine, Kubernetes Engine, App Engine oder Cloud Functions ausgeführt wird.

Für das folgende Beispiel müssen Sie die Cloud Storage-Clientbibliothek installieren.

C#

public object AuthImplicit(string projectId)
{
    // If you don't specify credentials when constructing the client, the
    // client library will look for credentials in the environment.
    var credential = GoogleCredential.GetApplicationDefault();
    var storage = StorageClient.Create(credential);
    // Make an authenticated API request.
    var buckets = storage.ListBuckets(projectId);
    foreach (var bucket in buckets)
    {
        Console.WriteLine(bucket.Name);
    }
    return null;
}

Go


// implicit uses Application Default Credentials to authenticate.
func implicit() {
	ctx := context.Background()

	// For API packages whose import path is starting with "cloud.google.com/go",
	// such as cloud.google.com/go/storage in this case, if there are no credentials
	// provided, the client library will look for credentials in the environment.
	storageClient, err := storage.NewClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	it := storageClient.Buckets(ctx, "project-id")
	for {
		bucketAttrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(bucketAttrs.Name)
	}

	// For packages whose import path is starting with "google.golang.org/api",
	// such as google.golang.org/api/cloudkms/v1, use NewService to create the client.
	kmsService, err := cloudkms.NewService(ctx)
	if err != nil {
		log.Fatal(err)
	}

	_ = kmsService
}

Java

static void authImplicit() {
  // If you don't specify credentials when constructing the client, the client library will
  // look for credentials via the environment variable GOOGLE_APPLICATION_CREDENTIALS.
  Storage storage = StorageOptions.getDefaultInstance().getService();

  System.out.println("Buckets:");
  Page<Bucket> buckets = storage.list();
  for (Bucket bucket : buckets.iterateAll()) {
    System.out.println(bucket.toString());
  }
}

Node.js

// Imports the Google Cloud client library.
const {Storage} = require('@google-cloud/storage');

// Instantiates a client. If you don't specify credentials when constructing
// the client, the client library will look for credentials in the
// environment.
const storage = new Storage();

try {
  // Makes an authenticated API request.
  const results = await storage.getBuckets();

  const [buckets] = results;

  console.log('Buckets:');
  buckets.forEach(bucket => {
    console.log(bucket.name);
  });
} catch (err) {
  console.error('ERROR:', err);
}

PHP

// Imports the Cloud Storage client library.
use Google\Cloud\Storage\StorageClient;

function auth_cloud_implicit($projectId)
{
    $config = [
        'projectId' => $projectId,
    ];

    # If you don't specify credentials when constructing the client, the
    # client library will look for credentials in the environment.
    $storage = new StorageClient($config);

    # Make an authenticated API request (listing storage buckets)
    foreach ($storage->buckets() as $bucket) {
        printf('Bucket: %s' . PHP_EOL, $bucket->name());
    }
}

Python

def implicit():
    from google.cloud import storage

    # If you don't specify credentials when constructing the client, the
    # client library will look for credentials in the environment.
    storage_client = storage.Client()

    # Make an authenticated API request
    buckets = list(storage_client.list_buckets())
    print(buckets)

Ruby

# project_id = "Your Google Cloud project ID"

require "google/cloud/storage"

# If you don't specify credentials when constructing the client, the client
# library will look for credentials in the environment.
storage = Google::Cloud::Storage.new project: project_id

# Make an authenticated API request
storage.buckets.each do |bucket|
  puts bucket.name
end

Dieses Vorgehen ist beim Testen und Experimentieren hilfreich. Es kann aber andererseits die Ermittlung der von Ihrer Anwendung verwendeten Anmeldedaten erschweren. Es wird empfohlen, explizit anzugeben, welche Anmeldedaten die Anwendung verwenden soll, wie im folgenden Abschnitt beschrieben.

Dienstkonto-Anmeldedaten manuell abrufen und bereitstellen

Wenn Sie Code lokal entwickeln oder Ihre Anwendung vor Ort bzw. in einer anderen öffentlichen Cloud bereitstellen, können Sie Anmeldedaten für Dienstkonten manuell erstellen und abrufen.

Dienstkonto erstellen

Die folgenden Schritte zeigen, wie ein Dienstkonto erstellt wird. Statt Berechtigungen auf Inhaberebene festzulegen, sollten Sie dabei den Zugriff auf die Berechtigungen einschränken. Eine Erläuterung dazu finden Sie im Abschnitt Zugriff einschränken.

Anmeldedaten für Dienstkonten bereitstellen

Nach dem Erstellen eines Dienstkontos haben Sie zwei Möglichkeiten, die Anmeldedaten für Ihre Anwendung bereitzustellen. Sie können entweder die Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS explizit festlegen oder den Pfad zum Dienstkontoschlüssel im Code übergeben.

Umgebungsvariable festlegen

Die Anmeldedaten zur Authentifizierung für Ihren Anwendungscode geben Sie durch Festlegung der Umgebungsvariable GOOGLE_APPLICATION_CREDENTIALS an. Für [PATH] geben Sie den Dateipfad der JSON-Datei an, die Ihren Dienstkontoschlüssel enthält, und für [FILE_NAME] den Dateinamen. Diese Variable gilt nur für Ihre aktuelle Shellsitzung. Wenn Sie eine neue Sitzung öffnen, müssen Sie die Variable erneut festlegen.

Linux oder macOS

export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

Beispiel:

export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Windows

Mit PowerShell:

$env:GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

Beispiel:

$env:GOOGLE_APPLICATION_CREDENTIALS="C:\Users\username\Downloads\[FILE_NAME].json"

Mit Eingabeaufforderung:

set GOOGLE_APPLICATION_CREDENTIALS=[PATH]

Nachdem Sie die oben genannten Schritte ausgeführt haben, können Ihre Anmeldedaten ermittelt werden, wie oben im Abschnitt Anmeldedaten für Ihre Anwendung bereitstellen erläutert. Diese Methode wird empfohlen, da sie weniger Code erfordert. Durch die Verwendung der Umgebungsvariablen lässt sich der Code zudem einfacher portieren, da Sie denselben Code in mehreren Umgebungen mit unterschiedlichen Dienstkonten ausführen können.

Den Pfad zum Dienstkontoschlüssel im Code übergeben

Alternativ können Sie im Code explizit auf Ihre Dienstkontodatei verweisen. Dies wird im folgenden Beispiel gezeigt.

Für das folgende Beispiel müssen Sie die Cloud Storage-Clientbibliothek installieren.

C#

// Some APIs, like Storage, accept a credential in their Create()
// method.
public object AuthExplicit(string projectId, string jsonPath)
{
    // Explicitly use service account credentials by specifying
    // the private key file.
    var credential = GoogleCredential.FromFile(jsonPath);
    var storage = StorageClient.Create(credential);
    // Make an authenticated API request.
    var buckets = storage.ListBuckets(projectId);
    foreach (var bucket in buckets)
    {
        Console.WriteLine(bucket.Name);
    }
    return null;
}
// Other APIs, like Language, accept a channel in their Create()
// method.
public object AuthExplicit(string projectId, string jsonPath)
{
    var credential = GoogleCredential.FromFile(jsonPath)
        .CreateScoped(LanguageServiceClient.DefaultScopes);
    var channel = new Grpc.Core.Channel(
        LanguageServiceClient.DefaultEndpoint.ToString(),
        credential.ToChannelCredentials());
    var client = LanguageServiceClient.Create(channel);
    AnalyzeSentiment(client);
    return 0;
}

Go


// explicit reads credentials from the specified path.
func explicit(jsonPath, projectID string) {
	ctx := context.Background()
	client, err := storage.NewClient(ctx, option.WithCredentialsFile(jsonPath))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Buckets:")
	it := client.Buckets(ctx, projectID)
	for {
		battrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(battrs.Name)
	}
}

Java

static void authExplicit(String jsonPath) throws IOException {
  // You can specify a credential file by providing a path to GoogleCredentials.
  // Otherwise credentials are read from the GOOGLE_APPLICATION_CREDENTIALS environment variable.
  GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(jsonPath))
        .createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));
  Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService();

  System.out.println("Buckets:");
  Page<Bucket> buckets = storage.list();
  for (Bucket bucket : buckets.iterateAll()) {
    System.out.println(bucket.toString());
  }
}

Node.js

// Imports the Google Cloud client library.
const {Storage} = require('@google-cloud/storage');

// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// const projectId = 'project-id'
// const keyFilename = '/path/to/keyfile.json'
const storage = new Storage({projectId, keyFilename});

// Makes an authenticated API request.
try {
  const [buckets] = await storage.getBuckets();

  console.log('Buckets:');
  buckets.forEach(bucket => {
    console.log(bucket.name);
  });
} catch (err) {
  console.error('ERROR:', err);
}

PHP

namespace Google\Cloud\Samples\Auth;

// Imports the Cloud Storage client library.
use Google\Cloud\Storage\StorageClient;

function auth_cloud_explicit($projectId, $serviceAccountPath)
{
    # Explicitly use service account credentials by specifying the private key
    # file.
    $config = [
        'keyFilePath' => $serviceAccountPath,
        'projectId' => $projectId,
    ];
    $storage = new StorageClient($config);

    # Make an authenticated API request (listing storage buckets)
    foreach ($storage->buckets() as $bucket) {
        printf('Bucket: %s' . PHP_EOL, $bucket->name());
    }
}

Python

def explicit():
    from google.cloud import storage

    # Explicitly use service account credentials by specifying the private key
    # file.
    storage_client = storage.Client.from_service_account_json(
        'service_account.json')

    # Make an authenticated API request
    buckets = list(storage_client.list_buckets())
    print(buckets)

Ruby

# project_id = "Your Google Cloud project ID"
# key_file   = "path/to/service-account.json"
require "google/cloud/storage"

# Explicitly use service account credentials by specifying the private key
# file.
storage = Google::Cloud::Storage.new project: project_id, keyfile: key_file

# Make an authenticated API request
storage.buckets.each do |bucket|
  puts bucket.name
end

Anmeldedaten in Compute Engine, Kubernetes Engine, der flexiblen App Engine-Umgebung und Cloud Functions abrufen

Wenn Ihre Anwendung in Compute Engine, Kubernetes Engine, der flexiblen App Engine-Umgebung oder Cloud Functions ausgeführt wird, müssen Sie kein eigenes Dienstkonto anlegen. Compute Engine enthält ein Standarddienstkonto, das automatisch für Sie erstellt wird. Bei Bedarf können Sie jeder Instanz ein anderes Dienstkonto zuweisen. Wenn Sie eine neue Instanz erstellen, wird diese automatisch als Standarddienstkonto mit einem Standardsatz von Autorisierungsberechtigungen ausgeführt. Weitere Informationen finden Sie unter Standardmäßiges Compute Engine-Dienstkonto.

Nachdem Sie ein Dienstkonto eingerichtet haben, können Ihre Anmeldedaten mit dem oben beschriebenen Vorgehen implizit ermittelt werden, ohne dass Sie Ihren Code ändern müssen. Wenn Sie explizit Compute Engine-Anmeldedaten verwenden möchten, gehen Sie dazu vor, wie im folgenden Codebeispiel gezeigt.

Für das folgende Beispiel müssen Sie die Cloud Storage-Clientbibliothek installieren.

C#

// Some APIs, like Storage, accept a credential in their Create()
// method.
public object AuthExplicitComputeEngine(string projectId)
{
    // Explicitly request service account credentials from the compute
    // engine instance.
    GoogleCredential credential =
        GoogleCredential.FromComputeCredential();
    var storage = StorageClient.Create(credential);
    // Make an authenticated API request.
    var buckets = storage.ListBuckets(projectId);
    foreach (var bucket in buckets)
    {
        Console.WriteLine(bucket.Name);
    }
    return null;
}
// Other APIs, like Language, accept a channel in their Create()
// method.
public object AuthExplicitComputeEngine(string projectId)
{
    var credential = GoogleCredential.FromComputeCredential();
    var channel = new Grpc.Core.Channel(
        LanguageServiceClient.DefaultEndpoint.ToString(),
        credential.ToChannelCredentials());
    var client = LanguageServiceClient.Create(channel);
    AnalyzeSentiment(client);
    return 0;
}

Go


// explicitDefault finds the default credentials.
//
// It is very uncommon to need to explicitly get the default credentials in Go.
// Most of the time, client libraries can use Application Default Credentials
// without having to pass the credentials in directly. See implicit above.
func explicitDefault(projectID string) {
	ctx := context.Background()

	creds, err := google.FindDefaultCredentials(ctx, storage.ScopeReadOnly)
	if err != nil {
		log.Fatal(err)
	}
	client, err := storage.NewClient(ctx, option.WithCredentials(creds))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Buckets:")
	it := client.Buckets(ctx, projectID)
	for {
		battrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(battrs.Name)
	}
}

Java

static void authCompute() {
  // Explicitly request service account credentials from the compute engine instance.
  GoogleCredentials credentials = ComputeEngineCredentials.create();
  Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService();

  System.out.println("Buckets:");
  Page<Bucket> buckets = storage.list();
  for (Bucket bucket : buckets.iterateAll()) {
    System.out.println(bucket.toString());
  }
}

Node.js

'use strict';

const {auth, Compute} = require('google-auth-library');

/**
 * This example directly instantiates a Compute client to acquire credentials.
 * Generally, you wouldn't directly create this class, rather call the
 * `auth.getClient()` method to automatically obtain credentials.
 */
async function main() {
  const client = new Compute({
    // Specifying the serviceAccountEmail is optional. It will use the default
    // service account if one is not defined.
    serviceAccountEmail: 'some-service-account@example.com',
  });
  const projectId = await auth.getProjectId();
  const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
  const res = await client.request({url});
  console.log(res.data);
}

main().catch(console.error);

PHP

namespace Google\Cloud\Samples\Auth;

// Imports GCECredentials and the Cloud Storage client library.
use Google\Auth\Credentials\GCECredentials;
use Google\Cloud\Storage\StorageClient;

function auth_cloud_explicit_compute_engine($projectId)
{
    $gceCredentials = new GCECredentials();
    $config = [
        'projectId' => $projectId,
        'credentialsFetcher' => $gceCredentials,
    ];
    $storage = new StorageClient($config);

    # Make an authenticated API request (listing storage buckets)
    foreach ($storage->buckets() as $bucket) {
        printf('Bucket: %s' . PHP_EOL, $bucket->name());
    }
}

Python

def explicit_compute_engine(project):
    from google.auth import compute_engine
    from google.cloud import storage

    # Explicitly use Compute Engine credentials. These credentials are
    # available on Compute Engine, App Engine Flexible, and Container Engine.
    credentials = compute_engine.Credentials()

    # Create the client using the credentials and specifying a project ID.
    storage_client = storage.Client(credentials=credentials, project=project)

    # Make an authenticated API request
    buckets = list(storage_client.list_buckets())
    print(buckets)

Ruby

require "googleauth"
require "google/cloud/env"
require "google/cloud/storage"

# Explicitly use Compute Engine credentials and a project ID to create a new
# Cloud Storage client. These credentials are available on Compute Engine,
# App Engine Flexible, and Container Engine.
storage = Google::Cloud::Storage.new project: Google::Cloud.env.project_id,
                                     keyfile: Google::Auth::GCECredentials.new

# Make an authenticated API request
storage.buckets.each do |bucket|
  puts bucket.name
end

Anmeldedaten in der App Engine-Standardumgebung abrufen

Wenn Ihre Anwendung in der App Engine-Standardumgebung ausgeführt wird, können Sie mit der App Engine App Identity API Anmeldedaten abrufen.

Nachdem Sie ein Dienstkonto eingerichtet haben, können Ihre Anmeldedaten mit dem oben beschriebenen Vorgehen implizit ermittelt werden, ohne dass Sie Ihren Code ändern müssen. Wenn Sie explizit App Engine-Anmeldedaten verwenden möchten, gehen Sie vor wie im folgenden Codebeispiel gezeigt.

Für das folgende Beispiel müssen Sie die Cloud Storage-Clientbibliothek installieren.

PHP

namespace Google\Cloud\Samples\Auth;

// Imports AppIdentityCredentials and the Cloud Storage client library.
use Google\Auth\Credentials\AppIdentityCredentials;
use Google\Cloud\Storage\StorageClient;

function auth_cloud_explicit_app_engine($projectId)
{
    # Learn more about scopes at https://cloud.google.com/storage/docs/authentication#oauth-scopes
    $scope = 'https://www.googleapis.com/auth/devstorage.read_only';
    $gaeCredentials = new AppIdentityCredentials($scope);
    $config = [
        'projectId' => $projectId,
        'credentialsFetcher' => $gaeCredentials,
    ];
    $storage = new StorageClient($config);

    # Make an authenticated API request (listing storage buckets)
    foreach ($storage->buckets() as $bucket) {
        printf('Bucket: %s' . PHP_EOL, $bucket->name());
    }
}

Go


// explicitDefault finds the default credentials.
//
// It is very uncommon to need to explicitly get the default credentials in Go.
// Most of the time, client libraries can use Application Default Credentials
// without having to pass the credentials in directly. See implicit above.
func explicitDefault(projectID string) {
	ctx := context.Background()

	creds, err := google.FindDefaultCredentials(ctx, storage.ScopeReadOnly)
	if err != nil {
		log.Fatal(err)
	}
	client, err := storage.NewClient(ctx, option.WithCredentials(creds))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Buckets:")
	it := client.Buckets(ctx, projectID)
	for {
		battrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(battrs.Name)
	}
}

Java

static void authAppEngineStandard() throws IOException {
  // Explicitly request service account credentials from the app engine standard instance.
  GoogleCredentials credentials = AppEngineCredentials.getApplicationDefault();
  Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService();

  System.out.println("Buckets:");
  Page<Bucket> buckets = storage.list();
  for (Bucket bucket : buckets.iterateAll()) {
    System.out.println(bucket.toString());
  }
}

Python

def explicit_app_engine(project):
    from google.auth import app_engine
    import googleapiclient.discovery

    # Explicitly use App Engine credentials. These credentials are
    # only available when running on App Engine Standard.
    credentials = app_engine.Credentials()

    # Explicitly pass the credentials to the client library.
    storage_client = googleapiclient.discovery.build(
        'storage', 'v1', credentials=credentials)

    # Make an authenticated API request
    buckets = storage_client.buckets().list(project=project).execute()
    print(buckets)

Zugriff einschränken

Gewähren Sie Ihrer Anwendung nur die Autorisierungsberechtigungen, die für die Interaktion mit den erforderlichen APIs, Features oder Ressourcen der GCP notwendig sind. GCP verwendet für die Zugriffssteuerung Cloud IAM (Identity and Access Management). Beim Erstellen eines Dienstkontos können Sie eine Cloud IAM-Rolle zur Beschränkung des Zugriffs auswählen. In der Anleitung müssen Sie beim Erstellen eines Dienstkontos unter Erste Schritte bei der Authentifizierung die Rolle Owner auswählen. Sie können diesen Wert jedoch jederzeit ändern. Weitere Informationen finden Sie unter Dienstkonten Rollen zuweisen.

Best Practices für die Verwaltung von Anmeldedaten

Anmeldedaten gewähren Zugriff auf sensible Daten. Mit den im Folgenden dargestellten Vorgehensweisen können Sie diese Ressourcen vor unberechtigtem Zugriff schützen.

  • Betten Sie in den Quellcode keine Secrets zur Authentifizierung wie API-Schlüssel, OAuth-Tokens und Anmeldedaten für Dienstkonten ein. Verwenden Sie dafür eine Umgebungsvariable, die auf Anmeldedaten außerhalb des Quellcodes der Anwendung verweist, zum Beispiel im Cloud Key Management Service.

  • Verwenden Sie unterschiedliche Anmeldedaten in unterschiedlichen Kontexten, beispielsweise in Test- und Produktionsumgebungen.

  • Übertragen Sie Anmeldedaten ausschließlich über HTTPS, sodass diese von Dritten nicht abgefangen werden können. Übertragen Sie Anmeldedaten niemals im Klartext oder als Teil der URL.

  • Betten Sie niemals langlebige Anmeldedaten in Ihre clientseitige Anwendung ein. Beispielsweise dürfen Anmeldedaten eines Dienstkontos nicht in eine mobile App eingebettet werden. Clientseitige Anwendungen können ausgewertet und Anmeldedaten auf einfache Weise von Dritten ermittelt sowie angewendet werden.

  • Widerrufen Sie ein Token, wenn Sie es nicht mehr benötigen.

API-Fehler beheben

Weitere Informationen zur Fehlerbehebung bei fehlgeschlagenen API-Anfragen finden Sie unter Cloud APIs – Fehler.

Weitere Informationen