Cloud Storage Client Library for Node.js that provides comprehensive API for managing buckets, files, and metadata with authentication, streaming, and access control.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Google Cloud Storage supports multiple authentication methods including HMAC keys for interoperability with Amazon S3-compatible tools, service accounts, and Application Default Credentials.
HMAC keys provide interoperable authentication that works with Amazon S3 libraries and tools, enabling a smooth migration path from S3 to Cloud Storage.
class HmacKey extends ServiceObject {
constructor(storage: Storage, accessId: string, options?: HmacKeyOptions);
// Properties
accessId: string;
storage: Storage;
projectId?: string;
userProject?: string;
// Methods
getMetadata(options?: GetMetadataOptions): Promise<HmacKeyMetadataResponse>;
setMetadata(metadata: SetHmacKeyMetadata, options?: SetHmacKeyMetadataOptions): Promise<HmacKeyMetadataResponse>;
delete(): Promise<[unknown]>;
}
interface HmacKeyOptions {
projectId?: string;
}interface HmacKeyMetadata {
accessId?: string;
etag?: string;
id?: string;
projectId?: string;
serviceAccountEmail?: string;
state?: 'ACTIVE' | 'INACTIVE' | 'DELETED';
timeCreated?: string;
updated?: string;
}
interface SetHmacKeyMetadata {
state?: 'ACTIVE' | 'INACTIVE';
etag?: string;
}
type HmacKeyMetadataResponse = [HmacKeyMetadata, unknown]; // [metadata, apiResponse]// Create HMAC key for service account
const serviceAccountEmail = 'service-account@project.iam.gserviceaccount.com';
const [hmacKey, secret] = await storage.createHmacKey(serviceAccountEmail);
console.log('Access ID:', hmacKey.accessId);
console.log('Secret Key (store securely):', secret);
console.log('Service Account:', hmacKey.metadata?.serviceAccountEmail);
console.log('State:', hmacKey.metadata?.state);
// Create with explicit project
const [hmacKey, secret] = await storage.createHmacKey(serviceAccountEmail, {
projectId: 'my-project-id'
});
// Store credentials securely
const credentials = {
accessKeyId: hmacKey.accessId,
secretAccessKey: secret,
endpoint: 'https://storage.googleapis.com'
};// Get HMAC key reference
const hmacKey = storage.hmacKey('GOOG1EXAMPLE123456789');
// Get metadata
const [metadata] = await hmacKey.getMetadata();
console.log('HMAC Key Info:');
console.log(' Access ID:', metadata.accessId);
console.log(' Service Account:', metadata.serviceAccountEmail);
console.log(' State:', metadata.state);
console.log(' Created:', metadata.timeCreated);
console.log(' Updated:', metadata.updated);
// Deactivate HMAC key
await hmacKey.setMetadata({
state: 'INACTIVE'
});
// Reactivate HMAC key
await hmacKey.setMetadata({
state: 'ACTIVE'
});
// Delete HMAC key (must be INACTIVE first)
await hmacKey.setMetadata({ state: 'INACTIVE' });
await hmacKey.delete();// List all HMAC keys
const [hmacKeys] = await storage.getHmacKeys();
hmacKeys.forEach(key => {
console.log(`Access ID: ${key.accessId}`);
console.log(`Service Account: ${key.metadata?.serviceAccountEmail}`);
console.log(`State: ${key.metadata?.state}`);
console.log('---');
});
// List keys for specific service account
const [hmacKeys] = await storage.getHmacKeys({
serviceAccountEmail: 'service-account@project.iam.gserviceaccount.com'
});
// Include deleted keys
const [allKeys] = await storage.getHmacKeys({
showDeletedKeys: true
});
// Stream HMAC keys for large lists
storage.getHmacKeysStream()
.on('data', hmacKey => {
console.log(`HMAC Key: ${hmacKey.accessId} - ${hmacKey.metadata?.state}`);
})
.on('end', () => {
console.log('Finished listing HMAC keys');
});
// List with pagination
const [hmacKeys, nextQuery] = await storage.getHmacKeys({
maxResults: 10
});
if (nextQuery) {
const [moreKeys] = await storage.getHmacKeys(nextQuery);
}// Using AWS SDK v3
import { S3Client, ListObjectsV2Command, PutObjectCommand } from '@aws-sdk/client-s3';
const s3Client = new S3Client({
credentials: {
accessKeyId: 'GOOG1EXAMPLE123456789', // HMAC access ID
secretAccessKey: 'base64-encoded-secret' // HMAC secret
},
endpoint: 'https://storage.googleapis.com',
region: 'auto', // Not used by Cloud Storage but required by AWS SDK
forcePathStyle: true // Use path-style URLs
});
// List objects
const listCommand = new ListObjectsV2Command({
Bucket: 'my-bucket',
Prefix: 'uploads/'
});
const response = await s3Client.send(listCommand);
// Upload object
const putCommand = new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'path/to/file.txt',
Body: 'File contents'
});
await s3Client.send(putCommand);# Using boto3 in Python
import boto3
client = boto3.client(
's3',
endpoint_url='https://storage.googleapis.com',
aws_access_key_id='GOOG1EXAMPLE123456789',
aws_secret_access_key='base64-encoded-secret'
)
# List objects
response = client.list_objects_v2(
Bucket='my-bucket',
Prefix='uploads/'
)
# Upload object
client.put_object(
Bucket='my-bucket',
Key='path/to/file.txt',
Body=b'File contents'
)# List bucket contents
curl -H "Authorization: AWS GOOG1EXAMPLE123456789:SIGNATURE" \
"https://storage.googleapis.com/my-bucket/"
# Upload file
curl -X PUT \
-H "Authorization: AWS GOOG1EXAMPLE123456789:SIGNATURE" \
-H "Content-Type: text/plain" \
--data "File contents" \
"https://storage.googleapis.com/my-bucket/path/to/file.txt"// Application Default Credentials (ADC)
// Automatically discovers credentials from environment
const storage = new Storage();
// ADC search order:
// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
// 2. gcloud user credentials
// 3. Google Cloud metadata server (on GCP)
// 4. Service account attached to the resource// Using service account key file
const storage = new Storage({
projectId: 'my-project-id',
keyFilename: '/path/to/service-account-key.json'
});
// Using credentials object
const storage = new Storage({
projectId: 'my-project-id',
credentials: {
type: 'service_account',
project_id: 'my-project-id',
private_key_id: 'key-id',
private_key: '-----BEGIN PRIVATE KEY-----\n...',
client_email: 'service@project.iam.gserviceaccount.com',
client_id: '123456789',
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
token_uri: 'https://oauth2.googleapis.com/token'
}
});// Set environment variables
process.env.GOOGLE_APPLICATION_CREDENTIALS = '/path/to/service-account.json';
process.env.GOOGLE_CLOUD_PROJECT = 'my-project-id';
// Storage client will automatically use these
const storage = new Storage();
// Or set in shell:
// export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
// export GOOGLE_CLOUD_PROJECT="my-project-id"import { GoogleAuth } from 'google-auth-library';
// Custom auth client
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
keyFile: '/path/to/service-account.json'
});
const authClient = await auth.getClient();
const storage = new Storage({
authClient: authClient
});
// With impersonation
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
credentials: {
// Source credentials
client_email: 'source@project.iam.gserviceaccount.com',
private_key: '...'
}
});
const client = await auth.getClient();
client.targetPrincipal = 'target@project.iam.gserviceaccount.com';
const storage = new Storage({
authClient: client
});import { OAuth2Client } from 'google-auth-library';
// OAuth client for user authentication
const oauth2Client = new OAuth2Client(
'client-id',
'client-secret',
'http://localhost:3000/callback'
);
// Generate auth URL
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://www.googleapis.com/auth/cloud-platform']
});
// After user authorization, exchange code for tokens
const { tokens } = await oauth2Client.getToken(authorizationCode);
oauth2Client.setCredentials(tokens);
const storage = new Storage({
authClient: oauth2Client
});// Don't hardcode credentials
// ❌ Bad
const storage = new Storage({
credentials: {
private_key: '-----BEGIN PRIVATE KEY-----\nActual key here...'
}
});
// ✅ Good - use environment variables
const storage = new Storage({
credentials: {
private_key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n')
}
});
// ✅ Best - use ADC with key file
process.env.GOOGLE_APPLICATION_CREDENTIALS = '/secure/path/to/key.json';
const storage = new Storage();// Create service accounts with minimal required permissions
// For read-only access
const readOnlyServiceAccount = {
roles: [
'roles/storage.objectViewer'
]
};
// For upload-only access
const uploadServiceAccount = {
roles: [
'roles/storage.objectCreator'
]
};
// For full bucket management
const bucketAdminServiceAccount = {
roles: [
'roles/storage.admin'
]
};// Regular HMAC key rotation
async function rotateHmacKey(serviceAccountEmail: string, oldAccessId: string) {
// Create new HMAC key
const [newHmacKey, newSecret] = await storage.createHmacKey(serviceAccountEmail);
// Update application configuration
await updateApplicationConfig({
accessKeyId: newHmacKey.accessId,
secretAccessKey: newSecret
});
// Test new credentials
const testStorage = new Storage({
// Use HMAC credentials with S3-compatible library
});
try {
await testStorage.getBuckets({ maxResults: 1 });
// Deactivate old key
const oldKey = storage.hmacKey(oldAccessId);
await oldKey.setMetadata({ state: 'INACTIVE' });
// Delete old key after grace period
setTimeout(async () => {
await oldKey.delete();
}, 24 * 60 * 60 * 1000); // 24 hours
} catch (error) {
// Rollback if new key doesn't work
await newHmacKey.setMetadata({ state: 'INACTIVE' });
await newHmacKey.delete();
throw error;
}
}// Enable audit logging for authentication events
// Configure in Cloud Console or via gcloud:
// gcloud logging sinks create storage-audit \
// bigquery.googleapis.com/projects/PROJECT_ID/datasets/audit_logs \
// --log-filter='protoPayload.serviceName="storage.googleapis.com"'
// Monitor HMAC key usage
async function auditHmacKeys() {
const [hmacKeys] = await storage.getHmacKeys();
for (const key of hmacKeys) {
const [metadata] = await key.getMetadata();
// Check for old or unused keys
const created = new Date(metadata.timeCreated);
const ageInDays = (Date.now() - created.getTime()) / (1000 * 60 * 60 * 24);
if (ageInDays > 90 && metadata.state === 'ACTIVE') {
console.warn(`HMAC key ${key.accessId} is ${ageInDays} days old and still active`);
}
if (metadata.state === 'INACTIVE' && ageInDays > 30) {
console.info(`Consider deleting inactive HMAC key ${key.accessId}`);
}
}
}import { ApiError } from '@google-cloud/storage';
try {
const [hmacKey, secret] = await storage.createHmacKey('invalid-email');
} catch (error) {
if (error instanceof ApiError) {
if (error.code === 400) {
console.log('Invalid service account email');
} else if (error.code === 403) {
console.log('Permission denied - check IAM roles');
} else if (error.code === 404) {
console.log('Service account not found');
} else {
console.error(`API Error ${error.code}: ${error.message}`);
}
}
}
// Test authentication
async function testAuthentication() {
try {
const [buckets] = await storage.getBuckets({ maxResults: 1 });
console.log('Authentication successful');
return true;
} catch (error) {
if (error instanceof ApiError) {
if (error.code === 401) {
console.error('Authentication failed - invalid credentials');
} else if (error.code === 403) {
console.error('Authentication successful but access denied');
} else {
console.error(`Authentication error: ${error.message}`);
}
}
return false;
}
}// S3 credentials
const s3Config = {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
region: 'us-west-2'
};
// Create equivalent HMAC key in Cloud Storage
const serviceAccount = 'migration-service@project.iam.gserviceaccount.com';
const [hmacKey, secret] = await storage.createHmacKey(serviceAccount);
const gcsConfig = {
accessKeyId: hmacKey.accessId, // Maps to S3 access key
secretAccessKey: secret, // Maps to S3 secret key
endpoint: 'https://storage.googleapis.com', // Cloud Storage endpoint
region: 'auto' // Not used but required by S3 SDK
};// Original S3 code
import AWS from 'aws-sdk';
const s3 = new AWS.S3({
accessKeyId: 'AKIA...',
secretAccessKey: '...',
region: 'us-west-2'
});
// Migrated to Cloud Storage with HMAC
import AWS from 'aws-sdk';
const s3 = new AWS.S3({
accessKeyId: 'GOOG1...', // HMAC access ID
secretAccessKey: '...', // HMAC secret
endpoint: 'https://storage.googleapis.com',
s3ForcePathStyle: true,
region: 'auto'
});
// Or migrate to native Cloud Storage client
import { Storage } from '@google-cloud/storage';
const storage = new Storage();
const bucket = storage.bucket('my-bucket');All HMAC key methods support both Promise and callback patterns:
// Promise pattern (recommended)
const [hmacKey, secret] = await storage.createHmacKey(serviceAccountEmail);
// Callback pattern
storage.createHmacKey(serviceAccountEmail, (err, hmacKey, secret, apiResponse) => {
if (err) {
console.error('Error:', err);
return;
}
console.log('Created HMAC key:', hmacKey.accessId);
console.log('Secret:', secret);
});
// Callback types
interface CreateHmacKeyCallback {
(err: Error | null, hmacKey?: HmacKey, secret?: string, apiResponse?: unknown): void;
}
interface HmacKeyMetadataCallback {
(err: Error | null, metadata?: HmacKeyMetadata, apiResponse?: unknown): void;
}