Skip to content

Commit

Permalink
feat: Connect to Redis With the ElastiCache IAM Credential Provider #361
Browse files Browse the repository at this point in the history
 (#365)

Co-authored-by: Aliaksandr Stsiapanay <[email protected]>
  • Loading branch information
astsiapanay and astsiapanay authored Jun 14, 2024
1 parent 3afa1ae commit cc55a5f
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 53 deletions.
80 changes: 43 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,43 +38,49 @@ Priority order:
2. File specified in "AIDIAL_SETTINGS" environment variable.
3. Default resource file: src/main/resources/aidial.settings.json.

| Setting | Default | Required | Description
|------------------|:-----------:|:----------:|-------------------------------------------------|
| config.files| aidial.config.json | No |List of paths to dynamic settings. Refer to [example](sample/aidial.config.json) of the file with [dynamic settings](#dynamic-settings).|
| config.reload | 60000 | No |Config reload interval in milliseconds.
| identityProviders | - | Yes |Map of identity providers. **Note**: At least one identity provider must be provided. Refer to [examples](sample/aidial.settings.json) to view available providers. Refer to [IDP Configuration](https://github.com/epam/ai-dial/blob/main/docs/Deployment/idp-configuration/auth0.md) to view guidelines for configuring supported providers.
| identityProviders.*.jwksUrl | - | Optional |Url to jwks provider. **Required** if `disabledVerifyJwt` is set to `false`. **Note**: Either `jwksUrl` or `userInfoEndpoint` must be provided.
| identityProviders.*.userInfoEndpoint | - | Optional |Url to user info endpoint. **Note**: Either `jwksUrl` or `userInfoEndpoint` must be provided or `disableJwtVerification` is unset. Refer to [Google example](sample/aidial.settings.json).
| identityProviders.*.rolePath | - | Yes |Path to the claim user roles in JWT token or user info response, e.g. `resource_access.chatbot-ui.roles` or just `roles`. Refer to [IDP Configuration](https://github.com/epam/ai-dial/blob/main/docs/Deployment/idp-configuration/auth0.md) to view guidelines for configuring supported providers.
| identityProviders.*.loggingKey | - | No |User information to search in claims of JWT token. `email` or `sub` should be sufficient in most cases. **Note**: `email` might be unavailable for some IdPs. Please check your IdP documentation in this case.
| identityProviders.*.loggingSalt | - | No |Salt to hash user information for logging.
| identityProviders.*.positiveCacheExpirationMs | 600000 | No | How long to retain JWKS response in the cache in case of successfull response.
| identityProviders.*.negativeCacheExpirationMs | 10000 | No |How long to retain JWKS response in the cache in case of failed response.
| identityProviders.*.issuerPattern | - | No |Regexp to match the claim "iss" to identity provider.
| identityProviders.*.disableJwtVerification | false | No |The flag disables JWT verification. *Note*. `userInfoEndpoint` must be unset if the flag is set to `true`.
| vertx.* | - | No |Vertx settings. Refer to [vertx.io](https://vertx.io/docs/apidocs/io/vertx/core/VertxOptions.html) to learn more.
| server.* | - | No |Vertx HTTP server settings for incoming requests.
| client.* | - | No |Vertx HTTP client settings for outbound requests.
| storage.provider | filesystem | Yes |Specifies blob storage provider. Supported providers: s3, aws-s3, azureblob, google-cloud-storage, filesystem. See examples in the sections below.
| storage.endpoint | - | Optional |Specifies endpoint url for s3 compatible storages. **Note**: The setting might be required. That depends on a concrete provider.
| storage.identity | - | Optional |Blob storage access key. Can be optional for filesystem, aws-s3, google-cloud-storage providers. Refer to [sections in this document](#aws-s3-blob-store) dedicated to specific storage providers.
| storage.credential | - | Optional |Blob storage secret key. Can be optional for filesystem, aws-s3, google-cloud-storage providers.
| storage.bucket | - | No |Blob storage bucket.
| storage.overrides.* | - | No |Key-value pairs to override storage settings. `*` might be any specific blob storage setting to be overridden. Refer to [examples](#temporary-credentials-1) in the sections below.
| storage.createBucket | false | No |Indicates whether bucket should be created on start-up.
| storage.prefix | - | No |Base prefix for all stored resources. The purpose to use the same bucket for different environments, e.g. dev, prod, pre-prod. Must not contain path separators or any invalid chars.
| encryption.password | - | No |Password used for AES encryption.
| encryption.salt | - | No |Salt used for AES encryption. The value should be random generated string.
| resources.maxSize | 1048576 | No |Max allowed size in bytes for a resource.
| resources.syncPeriod | 60000 | No |Period in milliseconds, how frequently check for resources to sync.
| resources.syncDelay | 120000 | No |Delay in milliseconds for a resource to be written back in object storage after last modification.
| resources.syncBatch | 4096 | No |How many resources to sync in one go.
| resources.cacheExpiration | 300000 | No |Expiration in milliseconds for synced resources in Redis.
| resources.compressionMinSize | 256 | No |Compress a resource with gzip if its size in bytes more or equal to this value.
| redis.singleServerConfig.address | - | Yes |Redis single server addresses, e.g. "redis://host:port". Either `singleServerConfig` or `clusterServersConfig` must be provided.
| redis.clusterServersConfig.nodeAddresses | - | Yes |Json array with Redis cluster server addresses, e.g. ["redis://host1:port1","redis://host2:port2"]. Either `singleServerConfig` or `clusterServersConfig` must be provided.
| invitations.ttlInSeconds | 259200 | No |Invitation time to live in seconds.
| access.admin.rules | - | No |Matches claims from identity providers with the rules to figure out whether a user is allowed to perform admin actions, like deleting any resource or approving a publication. Example: [{"source": "roles", "function": "EQUAL", "targets": ["admin"]}]. If roles contain "admin, the actions are allowed.
| Setting | Default | Required | Description
|-----------------------------------------------|:-----------:|:--------:|-------------------------------------------------|
| config.files | aidial.config.json | No |List of paths to dynamic settings. Refer to [example](sample/aidial.config.json) of the file with [dynamic settings](#dynamic-settings).|
| config.reload | 60000 | No |Config reload interval in milliseconds.
| identityProviders | - | Yes |Map of identity providers. **Note**: At least one identity provider must be provided. Refer to [examples](sample/aidial.settings.json) to view available providers. Refer to [IDP Configuration](https://github.com/epam/ai-dial/blob/main/docs/Deployment/idp-configuration/auth0.md) to view guidelines for configuring supported providers.
| identityProviders.*.jwksUrl | - | Optional |Url to jwks provider. **Required** if `disabledVerifyJwt` is set to `false`. **Note**: Either `jwksUrl` or `userInfoEndpoint` must be provided.
| identityProviders.*.userInfoEndpoint | - | Optional |Url to user info endpoint. **Note**: Either `jwksUrl` or `userInfoEndpoint` must be provided or `disableJwtVerification` is unset. Refer to [Google example](sample/aidial.settings.json).
| identityProviders.*.rolePath | - | Yes |Path to the claim user roles in JWT token or user info response, e.g. `resource_access.chatbot-ui.roles` or just `roles`. Refer to [IDP Configuration](https://github.com/epam/ai-dial/blob/main/docs/Deployment/idp-configuration/auth0.md) to view guidelines for configuring supported providers.
| identityProviders.*.loggingKey | - | No |User information to search in claims of JWT token. `email` or `sub` should be sufficient in most cases. **Note**: `email` might be unavailable for some IdPs. Please check your IdP documentation in this case.
| identityProviders.*.loggingSalt | - | No |Salt to hash user information for logging.
| identityProviders.*.positiveCacheExpirationMs | 600000 | No | How long to retain JWKS response in the cache in case of successfull response.
| identityProviders.*.negativeCacheExpirationMs | 10000 | No |How long to retain JWKS response in the cache in case of failed response.
| identityProviders.*.issuerPattern | - | No |Regexp to match the claim "iss" to identity provider.
| identityProviders.*.disableJwtVerification | false | No |The flag disables JWT verification. *Note*. `userInfoEndpoint` must be unset if the flag is set to `true`.
| vertx.* | - | No |Vertx settings. Refer to [vertx.io](https://vertx.io/docs/apidocs/io/vertx/core/VertxOptions.html) to learn more.
| server.* | - | No |Vertx HTTP server settings for incoming requests.
| client.* | - | No |Vertx HTTP client settings for outbound requests.
| storage.provider | filesystem | Yes |Specifies blob storage provider. Supported providers: s3, aws-s3, azureblob, google-cloud-storage, filesystem. See examples in the sections below.
| storage.endpoint | - | Optional |Specifies endpoint url for s3 compatible storages. **Note**: The setting might be required. That depends on a concrete provider.
| storage.identity | - | Optional |Blob storage access key. Can be optional for filesystem, aws-s3, google-cloud-storage providers. Refer to [sections in this document](#aws-s3-blob-store) dedicated to specific storage providers.
| storage.credential | - | Optional |Blob storage secret key. Can be optional for filesystem, aws-s3, google-cloud-storage providers.
| storage.bucket | - | No |Blob storage bucket.
| storage.overrides.* | - | No |Key-value pairs to override storage settings. `*` might be any specific blob storage setting to be overridden. Refer to [examples](#temporary-credentials-1) in the sections below.
| storage.createBucket | false | No |Indicates whether bucket should be created on start-up.
| storage.prefix | - | No |Base prefix for all stored resources. The purpose to use the same bucket for different environments, e.g. dev, prod, pre-prod. Must not contain path separators or any invalid chars.
| encryption.password | - | No |Password used for AES encryption.
| encryption.salt | - | No |Salt used for AES encryption. The value should be random generated string.
| resources.maxSize | 1048576 | No |Max allowed size in bytes for a resource.
| resources.syncPeriod | 60000 | No |Period in milliseconds, how frequently check for resources to sync.
| resources.syncDelay | 120000 | No |Delay in milliseconds for a resource to be written back in object storage after last modification.
| resources.syncBatch | 4096 | No |How many resources to sync in one go.
| resources.cacheExpiration | 300000 | No |Expiration in milliseconds for synced resources in Redis.
| resources.compressionMinSize | 256 | No |Compress a resource with gzip if its size in bytes more or equal to this value.
| redis.singleServerConfig.address | - | Yes |Redis single server addresses, e.g. "redis://host:port". Either `singleServerConfig` or `clusterServersConfig` must be provided.
| redis.clusterServersConfig.nodeAddresses | - | Yes |Json array with Redis cluster server addresses, e.g. ["redis://host1:port1","redis://host2:port2"]. Either `singleServerConfig` or `clusterServersConfig` must be provided.
| redis.provider.* | - | No |Provider specific settings
| redis.provider.name | - | Yes |Provider name. The valid values are `aws-elasti-cache`(see [instructions](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/auth-iam.html)).
| redis.provider.userId | - | Yes | IAM-enabled user ID. **Note**. It's applied to `aws-elasti-cache`
| redis.provider.region | - | Yes | Geo region where the cache is located. **Note**. It's applied to `aws-elasti-cache`
| redis.provider.clusterName | - | Yes | Redis cluster name. **Note**. It's applied to `aws-elasti-cache`
| redis.provider.serverless | - | Yes | The flag indicates if the cache is serverless. **Note**. It's applied to `aws-elasti-cache`
| invitations.ttlInSeconds | 259200 | No |Invitation time to live in seconds.
| access.admin.rules | - | No |Matches claims from identity providers with the rules to figure out whether a user is allowed to perform admin actions, like deleting any resource or approving a publication. Example: [{"source": "roles", "function": "EQUAL", "targets": ["admin"]}]. If roles contain "admin, the actions are allowed.

### Storage requirements

Expand Down
18 changes: 2 additions & 16 deletions src/main/java/com/epam/aidial/core/AiDial.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.epam.aidial.core;

import com.epam.aidial.core.cache.CacheClientFactory;
import com.epam.aidial.core.config.ConfigStore;
import com.epam.aidial.core.config.Encryption;
import com.epam.aidial.core.config.FileConfigStore;
Expand Down Expand Up @@ -44,10 +45,7 @@
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.ConfigSupport;

import java.io.FileInputStream;
import java.io.IOException;
Expand Down Expand Up @@ -109,7 +107,7 @@ void start() throws Exception {
EncryptionService encryptionService = new EncryptionService(Json.decodeValue(settings("encryption").toBuffer(), Encryption.class));
TokenStatsTracker tokenStatsTracker = new TokenStatsTracker();

redis = openRedis();
redis = CacheClientFactory.create(settings("redis"));

LockService lockService = new LockService(redis, storage.getPrefix());
resourceService = new ResourceService(vertx, redis, storage, lockService, settings("resources"), storage.getPrefix());
Expand Down Expand Up @@ -143,18 +141,6 @@ void start() throws Exception {
}
}

private RedissonClient openRedis() throws IOException {
JsonObject conf = settings("redis");
if (conf.isEmpty()) {
throw new IllegalArgumentException("Redis configuration not found");
}

ConfigSupport support = new ConfigSupport();
Config config = support.fromJSON(conf.toString(), Config.class);

return Redisson.create(config);
}

@VisibleForTesting
void stop() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.epam.aidial.core.cache;

import com.amazonaws.auth.AWSCredentialsProvider;
import org.redisson.config.Credentials;
import org.redisson.config.CredentialsResolver;

import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

/**
* Source of implementation <a href="https://redisson.org/articles/how-to-connect-to-redis-using-elasticache-iam-credential-provider.html">
* How to Connect to Redis With the ElastiCache IAM Credential Provider</a>.
*/
public class AwsCredentialsResolver implements CredentialsResolver {

private static final long TOKEN_EXPIRY_MS = 900_000;

private final String userName;
private final IamAuthTokenRequest iamAuthTokenRequest;
private final AWSCredentialsProvider awsCredentialsProvider;

private volatile CompletionStage<Credentials> future;
private volatile long lastTime = System.currentTimeMillis();

public AwsCredentialsResolver(String userName, IamAuthTokenRequest iamAuthTokenRequest,
AWSCredentialsProvider awsCredentialsProvider) {
this.userName = userName;
this.iamAuthTokenRequest = iamAuthTokenRequest;
this.awsCredentialsProvider = awsCredentialsProvider;
}

@Override
public CompletionStage<Credentials> resolve(InetSocketAddress address) {
if (System.currentTimeMillis() - lastTime > TOKEN_EXPIRY_MS || future == null) {
try {
String token = iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.getCredentials());
future = CompletableFuture.completedFuture(new Credentials(userName, token));
lastTime = System.currentTimeMillis();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
return future;
}
}
Loading

0 comments on commit cc55a5f

Please sign in to comment.