Secrets management with Squid AI

Secrets management with Squid AI

In this blog post we'll delve into the capabilities of Squid AI, focusing on its robust built-in secret management system. You'll learn how to adeptly manage sensitive information, secure endpoints, and efficiently rotate API keys, all integral components of maintaining a secure and efficient digital environment.

Security and simplicity: Core principles of Squid

In designing Squid, a significant emphasis was placed on achieving a balance between robust security and user-friendly simplicity. This approach is particularly evident in the platform's handling of sensitive information such as passwords, API keys, and access tokens.

Managing secrets in Squid

Squid offers versatile tools for secret management, enabling the secure handling of various sensitive elements crucial for application security. This includes everything from API keys and passwords to access tokens. This is crucial in today's digital landscape, where securing sensitive information is paramount.

The Squid Client SDK enhances this capability by offering programmatic secret management. This allows developers to automate security processes like rotating API keys, refreshing passwords, and dynamically managing services. With Squid, handling these elements becomes seamless, ensuring your application's security and compliance needs are met efficiently.

This approach to secret management ensures that sensitive information is stored and accessed securely, minimizing the risk of data breaches and unauthorized access. Squid's comprehensive tools make it effortless to integrate robust secret management into your application's workflow.

For a more detailed understanding of how to manage secrets using Squid, be sure to explore the Squid Documentation on Secret Management.

Expose an API and Secure it

In this blog post we will demonstrate how to easily expose an API that serves secured user information from a database in a secure way without any boilerplate code, let’s dive in.

This is a simple example and you may want to extend it or modify to serve more complex data from multiple sources or with more parameters in the request.

Getting started

Before you move forward, you need to set up a Squid application along with a backend. Start this process by creating an application in the Squid Console, then follow the step-by-step instructions on the application overview page to get your Squid Backend project up and running.

Alternatively, you can also watch this short video tutorial that explains the basics of how to initialize and utilize the Squid backend.

Creating and accessing a Secret

The Squid Client SDK offers comprehensive tools to manage all your app's secrets effectively. For detailed information, refer to the complete API documentation. Here are some examples to illustrate this functionality:

Creating a Secret:

import { Squid } from '@squidcloud/client';
// Note that initializing Squid with an apiKey provides an admin access
const squid = new Squid({ appId: 'YOUR_APP_ID', region: 'YOUR_REGION', apiKey: 'YOUR_API_KEY' });
// Creating a secret
await squid.secrets.upsert('mySecretKey', 'mySecretValue');

Accessing the Secret in a function:

const secret = await squid.secrets.get('mySecretKey');
if (secret) {
  console.log('Secret Value:', secret.value);
}

Creating an API

Squid offers several methods to expose APIs, with Webhooks and OpenAPI being the primary choices. Webhooks are ideal for exposing a single endpoint, often facilitating system-to-system communication. On the other hand, OpenAPI serves as a standard for detailing and exposing APIs, specifying request/response schemas and other API facets.

Here, we'll demonstrate how to set up a webhook to retrieve data from a database using a query parameter. Let's begin with the following code example:

import { WebhookRequest, WebhookResponse } from '@squidcloud/client';
import { limits, webhook, SquidService } from '@squidcloud/backend';

class MyService extends SquidService {

  @webhook('getUserDetails')
  @limits(
    { rateLimit: { value: 5, scope: 'ip' },
    { quotaLimit: { value: 10000, scope: 'ip' } }
  )
  async getUserDetails(request: WebhookRequest): Promise<WebhookResponse> {
    const userId = request.queryParams['userId'];
    if (!userId) {
      return this.createWebhookResponse('BAD_REQUEST', 400);
    }
    // This exmaple uses the Squid built in DB but you can easily connect your own DB or API
    return await this.squid.collection('userDetails').doc(userId).snapshot();
  }
}

In this example, WebhookRequest and WebhookResponse are used to handle the incoming request and the response from the webhook, respectively. The @limits decorator protects the endpoint from abuse. In this configuration, we enable up to 5 requests per second per IP address, with a monthly allowance of 10,000 requests for each IP. For more details on this feature, consult our limits decorator documentation.

To read more about webhooks, refer to the documentation.

Implementing API key verification

Ensuring secure access to these APIs is crucial because they may expose or modify sensitive information. A simple yet effective mechanism is to require an API key for access. This can be implemented by validating a query parameter in the API key.

Below is an example of how to use a webhook and the secrets to validate an API key, ensuring that only authorized users or systems can access it.

You may create an API key in the Squid Console under the “Secrets” menu or programmatically and then validate it as part of the webhook function:

import { WebhookRequest, WebhookResponse } from '@squidcloud/client';
import { webhook, SquidService } from '@squidcloud/backend';

class MyService extends SquidService {

  @webhook('getUserDetails')
  async getUserDetails(request: WebhookRequest): Promise<WebhookResponse> {
    const apiKey = request.queryParams['apiKey'];
    if (!apiKey || apiKey !== this.secrets['USER_DETAILS_API_KEY']) {
      return this.createWebhookResponse('UNAUTHORIZED', 401);
    }

    const userId = request.queryParams['userId'];
    if (!userId) {
      return this.createWebhookResponse('BAD_REQUEST', 400);
    }
    // This exmaple uses the Squid built in DB but you can easily connect your own DB or API
    return await this.squid.collection('userDetails').doc(userId).snapshot();
  }
}

In this example, the this.secrets['USER_DETAILS_API_KEY'] refers to a securely stored API key in Squid, which is checked against the provided API key in the request. This approach ensures that only requests with the correct API key can access the webhook functionality.

Implementing API key rotation with a Scheduler in Squid

Effective and secure management of API keys is essential for maintaining application security. Squid enables the creation of an API key rotation system with its scheduler feature, ensuring that API keys are regularly updated. This rotation process minimizes risks associated with potential API key exposure by ensuring that keys are only valid for a set period. By storing four API keys, each marked with a suffix from 1 to 4, the system maintains an organized sequence from the most recent to the oldest.

Implementing API Key Rotation

To initiate key rotation, a scheduler is configured to update these keys systematically:

import { SquidService, scheduler } from '@squidcloud/backend';
import { CronExpression } from '@squidcloud/client';
import { randomBytes } from 'crypto';

export class ApiKeyRotationService extends SquidService {

  @scheduler('rotateUserDetailsApiKeys', CronExpression.EVERY_WEEK)
  async rotateUserDetailsApiKeys(): Promise<void> {
    const apiKeyPrefix = 'USER_DETAILS_API_KEY';
    // Retrieve the current keys
    const key1 = this.secrets[`$(apiKeyPrefix}_1`];
    const key2 = this.secrets[`$(apiKeyPrefix}_2`];
    const key3 = this.secrets[`$(apiKeyPrefix}_3`];

    const entries = [
      { key: `$(apiKeyPrefix}_4`, value: key3 },
      { key: `$(apiKeyPrefix}_3`, value: key2 },
      { key: `$(apiKeyPrefix}_2`, value: key1 },
      { key: `$(apiKeyPrefix}_1`, value: randomBytes(16).toString('hex') } // Generates a 32-character hexadecimal string
    ];
    await this.squid.secrets.upsertMany(entries);
  }
}

Webhook implementation for API key validation

With several API keys active simultaneously, it's necessary to verify the API key in the request against all currently valid keys. This verification can be achieved by introducing a validateApiKey method. This method takes an API key prefix and the API key from the request as inputs and returns whether there's a match.

import { webhook, SquidService } from '@squidcloud/backend';
import { WebhookRequest, WebhookResponse } from '@squidcloud/client';

class MyService extends SquidService {

  @webhook('myWebhook')
  async myWebhook(request: WebhookRequest): Promise<WebhookResponse> {
    const apiKeyFromRequest = request.queryParams['apiKey'];
    if (!this.validateApiKey('USER_DETAILS_API_KEY', apiKeyFromRequest)) {
      return this.createWebhookResponse('UNAUTHORIZED', 401);
    }

    const userId = request.queryParams['userId'];
    if (!userId) {
      return this.createWebhookResponse('BAD_REQUEST', 400, );
    }
    // This exmaple uses the Squid built in DB but you can easily connect your own DB or API
    return await this.squid.collection('userDetails').doc(userId).snapshot();
  }

  private validateApiKey(apiKeyPrefix: string, apiKeyFromRequest: string): boolean {
        for (let i = 1; i <= 4; i++) {
        const currentKey = this.secrets[`${apiKeyPrefix}_${i}`];
        if (currentKey === apiKeyFromRequest) {
          return true; // API key matches one of the stored keys
        }
      }
      return false; // No match found
  }
}

With this setup, Squid not only securely stores API keys but also ensures their regular rotation, greatly enhancing your application's security. This method combines the ease of Squid's secret management with the power of scheduled tasks for efficient API key management.

Lastly, you will probably want to expose an endpoint for different systems to provide an API key and retrieve the latest API key. This can easily achieved using another webhook:

import { webhook, SquidService } from '@squidcloud/backend';
import { WebhookRequest, WebhookResponse } from '@squidcloud/client';

class ApiKeyService extends SquidService {
  @webhook('getLatestUserDetailsApiKey')
  async getLatestUserDetailsApiKey(request: WebhookRequest): Promise<WebhookResponse> {
    const providedApiKey = request.queryParams['apiKey'];

    const apiKeyPrefix = 'USER_DETAILS_API_KEY'
    // Validate the provided API key
    if (!this.validateApiKey(apiKeyPrefix, providedApiKey)) {
      return this.createWebhookResponse('UNAUTHORIZED', 401);
    }

    // Retrieve and return the latest API key
    const latestApiKey = this.secrets[`${apiKeyPrefix}_1`];
    return this.createWebhookResponse({ latestApiKey }, 200);
  }
}

Conclusion

Squid AI offers a comprehensive, secure, and easy-to-use environment for managing backend processes, including the delicate handling of API keys and secrets. By leveraging Squid's capabilities, developers can maintain high security standards while focusing on building great applications.