aws / aws-sdk

Landing page for the AWS SDKs on GitHub
https://aws.amazon.com/tools/
Other
68 stars 13 forks source link

Redis Signer for connecting to Redis 7 using IAM authentication #556

Open meenar-se opened 1 year ago

meenar-se commented 1 year ago

Describe the feature

Need an implementation for Redis signer so that its useful to connect to Elastic cache redis version 7 or above using IAM Authentication.

Use Case

Recently AWS introduced an option to connect to Redis 7 using IAM authentication. But its not directly supported by the library. So we have slightly tweaked the RDS signer to support Redis as well. It will be useful if we add the Redis Signer as well in library itself.

Proposed Solution

No response

Other Information

No response

Acknowledgements

SDK version used

3.350.0

Environment details (OS name and version, etc.)

Macos 12.6.3

RanVaknin commented 1 year ago

Hi @meenar-se ,

Thanks for opening this feature request.

I think your request is reasonable, but like RDS signer, this would need to be a handwritten utility (whereas most of the SDK is code generated from the API models of the each service), this means that it will have to be properly designed and implemented in a uniform fashion across all SDKs.

Additionally, feature requests are accepted and implemented based on community engagement (comments, upvotes, or duplicate FRs). This helps us prioritize features in the most impactful way, and use the teams resources wisely.

I will transfer this FR to the cross SDK repo for it to gain traction there. This unfortunately means that it will not get prioritized right away, but it doesn't mean it wont in the future.

Since you marked the "I may be able to implement this feature request" checkbox, I'd encourage you to write your implementation here on this ticket for two reasons:

  1. It will allow other community members that are facing the same issue to use your solution as a workaround.
  2. When time comes to execute on this feature request, it will be a good starting points for one of the devs to refer to and test against.

Thanks again 😄 Ran

meenar-se commented 1 year ago

Implementation

Configuration file: runtimeConfig.ts

import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { Hash } from '@aws-sdk/hash-node';
import { loadConfig } from '@aws-sdk/node-config-provider';
import { SignerConfig } from './Signer';

/**
 * @internal
 */
export const getRuntimeConfig = (config: SignerConfig) : SignerConfig => ({
  runtime: 'node',
  sha256: config?.sha256 ?? Hash.bind(null, 'sha256'),
  credentials: config?.credentials ?? fromNodeProviderChain(),
  region: config?.region ?? loadConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS),
  expiresIn: 900,
  ...config,
} as SignerConfig);

Signer Implementation: Signer.ts

import { SignatureV4 } from '@aws-sdk/signature-v4';
import {
  AwsCredentialIdentity,
  AwsCredentialIdentityProvider,
  ChecksumConstructor,
} from '@aws-sdk/types';
import { formatUrl } from '@aws-sdk/util-format-url';

import { getRuntimeConfig as __getRuntimeConfig } from './runtimeConfig';
import { defaultProvider } from '@aws-sdk/credential-provider-node';

export interface SignerConfig {
  /**
   * The AWS credentials to sign requests with. Uses the default credential provider chain if not specified.
   */
  credentials?: AwsCredentialIdentity | AwsCredentialIdentityProvider
  /**
   * The hostname of the database to connect to.
   */
  hostname: string
  /**
   * The port number the database is listening on.
   */
  port?: number
  /**
   * The region the database is located in. Uses the region inferred from the runtime if omitted.
   */
  region?: string
  /**
   * The SHA256 hasher constructor to sign the request.
   */
  sha256?: ChecksumConstructor
  /**
   * The username to login as.
   */
  username: string
  runtime?: string
  expiresIn?: number
}

/**
 * The signer class that generates an auth token to a database.
 */
export class Signer {

  private readonly protocol: string = 'http:';
  private readonly service: string = 'elasticache';

  public constructor(public configuration: SignerConfig) {
    this.configuration = __getRuntimeConfig(configuration);

  }

  public async getAuthToken(): Promise<string> {
    const signer = new SignatureV4({
      service: this.service,
      region: this.configuration.region!,
      credentials: this.configuration.credentials ?? defaultProvider(),
      sha256: this.configuration.sha256!,
    });

    const request = new HttpRequest({
      method: 'GET',
      protocol: this.protocol,
      hostname: this.configuration.hostname,
      port: this.configuration.port,
      query: {
        Action: 'connect',
        User: this.configuration.username,   
      },
      headers: {
        host: `${this.configuration.hostname}`,
      },
    });

    const presigned = await signer.presign(request, {
      expiresIn: this.configuration.expiresIn,
    });
    const format = formatUrl(presigned).replace(`${this.protocol}//`, '');
    console.log(format);

    return format;
  }
}

Usage example


import { createClient } from "redis";
import { Signer } from "./Signer";

export const redisConnect = async () => {
  console.log("calling redis connect");
  const credentials = await generateAssumeRoleCreds();
  const sign = new Signer({
    region: region,
    hostname: `${replicationGroupId}`,
    username: userId,
    credentials: credentials,
  });
  const presignedUrl = await sign.getAuthToken();
  const redisConfig = {
    url: `redis://master.xxx.xxx.xxxx.use1.cache.amazonaws.com:6379`,
    password: presignedUrl,
    username: userId,
    socket: {
      tls: true,
      rejectUnauthorized: false,
    },
  };
  const redisClient = await createClient(redisConfig);
  try {
    await redisClient.connect();
    console.log(await redisClient.get("key"));
  } catch (error) {
    console.log("Error catched " + error);
  }
};
meenar-se commented 1 year ago

I can also work on the implementing this feature in SDK. We can have the signer as common utility and use it for REDIS and RDS. Please suggest.