Create GCP Service Account Keys

The gcp:serviceaccount/key:Key resource, part of the Pulumi GCP provider, generates authentication credentials for service accounts in JSON or PEM format. This guide focuses on two capabilities: basic key generation and automated rotation.

Service account keys require an existing service account and store sensitive credentials in Pulumi state. The examples are intentionally small. Combine them with your own service accounts and secure state storage.

Generate a service account key for authentication

Applications running outside Google Cloud need credentials to authenticate with Google Cloud APIs.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const myaccount = new gcp.serviceaccount.Account("myaccount", {
    accountId: "myaccount",
    displayName: "My Service Account",
});
const mykey = new gcp.serviceaccount.Key("mykey", {
    serviceAccountId: myaccount.name,
    publicKeyType: "TYPE_X509_PEM_FILE",
});
import pulumi
import pulumi_gcp as gcp

myaccount = gcp.serviceaccount.Account("myaccount",
    account_id="myaccount",
    display_name="My Service Account")
mykey = gcp.serviceaccount.Key("mykey",
    service_account_id=myaccount.name,
    public_key_type="TYPE_X509_PEM_FILE")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		myaccount, err := serviceaccount.NewAccount(ctx, "myaccount", &serviceaccount.AccountArgs{
			AccountId:   pulumi.String("myaccount"),
			DisplayName: pulumi.String("My Service Account"),
		})
		if err != nil {
			return err
		}
		_, err = serviceaccount.NewKey(ctx, "mykey", &serviceaccount.KeyArgs{
			ServiceAccountId: myaccount.Name,
			PublicKeyType:    pulumi.String("TYPE_X509_PEM_FILE"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var myaccount = new Gcp.ServiceAccount.Account("myaccount", new()
    {
        AccountId = "myaccount",
        DisplayName = "My Service Account",
    });

    var mykey = new Gcp.ServiceAccount.Key("mykey", new()
    {
        ServiceAccountId = myaccount.Name,
        PublicKeyType = "TYPE_X509_PEM_FILE",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.serviceaccount.Key;
import com.pulumi.gcp.serviceaccount.KeyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var myaccount = new Account("myaccount", AccountArgs.builder()
            .accountId("myaccount")
            .displayName("My Service Account")
            .build());

        var mykey = new Key("mykey", KeyArgs.builder()
            .serviceAccountId(myaccount.name())
            .publicKeyType("TYPE_X509_PEM_FILE")
            .build());

    }
}
resources:
  myaccount:
    type: gcp:serviceaccount:Account
    properties:
      accountId: myaccount
      displayName: My Service Account
  mykey:
    type: gcp:serviceaccount:Key
    properties:
      serviceAccountId: ${myaccount.name}
      publicKeyType: TYPE_X509_PEM_FILE

The serviceAccountId property references the service account that owns this key. The publicKeyType property controls the output format; TYPE_X509_PEM_FILE generates an X.509 certificate. The privateKey output contains the base64-encoded JSON credential file that applications use to authenticate.

Rotate keys automatically on a schedule

Security policies often require periodic credential rotation to limit exposure from compromised keys.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
import * as time from "@pulumiverse/time";

const myaccount = new gcp.serviceaccount.Account("myaccount", {
    accountId: "myaccount",
    displayName: "My Service Account",
});
// note this requires the terraform to be run regularly
const mykeyRotation = new time.Rotating("mykey_rotation", {rotationDays: 30});
const mykey = new gcp.serviceaccount.Key("mykey", {
    serviceAccountId: myaccount.name,
    keepers: {
        rotation_time: mykeyRotation.rotationRfc3339,
    },
});
import pulumi
import pulumi_gcp as gcp
import pulumiverse_time as time

myaccount = gcp.serviceaccount.Account("myaccount",
    account_id="myaccount",
    display_name="My Service Account")
# note this requires the terraform to be run regularly
mykey_rotation = time.Rotating("mykey_rotation", rotation_days=30)
mykey = gcp.serviceaccount.Key("mykey",
    service_account_id=myaccount.name,
    keepers={
        "rotation_time": mykey_rotation.rotation_rfc3339,
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/pulumiverse/pulumi-time/sdk/go/time"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		myaccount, err := serviceaccount.NewAccount(ctx, "myaccount", &serviceaccount.AccountArgs{
			AccountId:   pulumi.String("myaccount"),
			DisplayName: pulumi.String("My Service Account"),
		})
		if err != nil {
			return err
		}
		// note this requires the terraform to be run regularly
		mykeyRotation, err := time.NewRotating(ctx, "mykey_rotation", &time.RotatingArgs{
			RotationDays: pulumi.Int(30),
		})
		if err != nil {
			return err
		}
		_, err = serviceaccount.NewKey(ctx, "mykey", &serviceaccount.KeyArgs{
			ServiceAccountId: myaccount.Name,
			Keepers: pulumi.StringMap{
				"rotation_time": mykeyRotation.RotationRfc3339,
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
using Time = Pulumiverse.Time;

return await Deployment.RunAsync(() => 
{
    var myaccount = new Gcp.ServiceAccount.Account("myaccount", new()
    {
        AccountId = "myaccount",
        DisplayName = "My Service Account",
    });

    // note this requires the terraform to be run regularly
    var mykeyRotation = new Time.Rotating("mykey_rotation", new()
    {
        RotationDays = 30,
    });

    var mykey = new Gcp.ServiceAccount.Key("mykey", new()
    {
        ServiceAccountId = myaccount.Name,
        Keepers = 
        {
            { "rotation_time", mykeyRotation.RotationRfc3339 },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumiverse.time.Rotating;
import com.pulumiverse.time.RotatingArgs;
import com.pulumi.gcp.serviceaccount.Key;
import com.pulumi.gcp.serviceaccount.KeyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var myaccount = new Account("myaccount", AccountArgs.builder()
            .accountId("myaccount")
            .displayName("My Service Account")
            .build());

        // note this requires the terraform to be run regularly
        var mykeyRotation = new Rotating("mykeyRotation", RotatingArgs.builder()
            .rotationDays(30)
            .build());

        var mykey = new Key("mykey", KeyArgs.builder()
            .serviceAccountId(myaccount.name())
            .keepers(Map.of("rotation_time", mykeyRotation.rotationRfc3339()))
            .build());

    }
}
resources:
  myaccount:
    type: gcp:serviceaccount:Account
    properties:
      accountId: myaccount
      displayName: My Service Account
  # note this requires the terraform to be run regularly
  mykeyRotation:
    type: time:Rotating
    name: mykey_rotation
    properties:
      rotationDays: 30
  mykey:
    type: gcp:serviceaccount:Key
    properties:
      serviceAccountId: ${myaccount.name}
      keepers:
        rotation_time: ${mykeyRotation.rotationRfc3339}

The keepers property triggers new key generation when its values change. Here, a time.Rotating resource updates rotationRfc3339 every 30 days, causing Pulumi to generate a new key. This requires running Pulumi regularly (at least every 30 days) to detect when rotation is due.

Beyond these examples

These snippets focus on specific key-level features: key generation and format selection, and automated rotation with time-based triggers. They’re intentionally minimal rather than complete credential management solutions.

The examples reference pre-existing infrastructure such as service accounts with appropriate IAM permissions. They focus on key configuration rather than provisioning the service accounts themselves.

To keep things focused, common key patterns are omitted, including:

  • Key algorithm selection (keyAlgorithm)
  • Private key format options (privateKeyType)
  • Public key upload (publicKeyData)
  • Key lifecycle management (deletion, expiration handling)

These omissions are intentional: the goal is to illustrate how each key feature is wired, not provide drop-in credential modules. See the Service Account Key resource reference for all available configuration options.

Let's create GCP Service Account Keys

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Security & State Management
Why is storing service account keys in Pulumi state a security concern?
The privateKey is persisted in plaintext in your Pulumi state, creating a security risk. Ensure your state backend has encryption at rest and appropriate access controls.
Key Lifecycle & Rotation
Can I import existing service account keys into Pulumi?
No, this resource does not support import. You must create new keys through Pulumi or manage existing keys outside of Pulumi.
How do I automatically rotate service account keys on a schedule?
Use the keepers property with a time.Rotating resource. When the rotation time changes, Pulumi will generate a new key automatically.
When is the privateKey output available?
The privateKey output is only populated when creating a new key. It won’t be available for keys created outside Pulumi or after the initial creation.
Configuration & Formats
What formats can I use for serviceAccountId?
You can use three formats: {ACCOUNT} (email or name), projects/{PROJECT_ID}/serviceAccounts/{ACCOUNT}, or substitute - as a wildcard for {PROJECT_ID} to infer the project.
What are the default key formats?
The defaults are KEY_ALG_RSA_2048 for keyAlgorithm, TYPE_GOOGLE_CREDENTIALS_FILE for privateKeyType, and TYPE_X509_PEM_FILE for publicKeyType.
What conflicts exist when using publicKeyData?
The publicKeyData property conflicts with both publicKeyType and privateKeyType. Use publicKeyData for bringing your own public key, or use the type properties for Pulumi-generated keys.
Integration Patterns
Why shouldn't I store service account keys in Kubernetes Secrets?
This pattern is deprecated. Workload Identity is the recommended way of accessing Google Cloud APIs from pods, eliminating the need to store keys in Secrets.
What's the recommended alternative to storing keys in Kubernetes?
Use Workload Identity, which allows pods to authenticate to Google Cloud APIs without storing service account keys.

Using a different cloud?

Explore security guides for other cloud providers: