blob: 4524297be99414bbfcc931dd05a1d4125311b57d [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.plugin.credential.aws;
import static java.time.LocalDateTime.now;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.mariadb.jdbc.Configuration;
import org.mariadb.jdbc.HostAddress;
import org.mariadb.jdbc.plugin.Credential;
import org.mariadb.jdbc.plugin.CredentialPlugin;
/**
* Permit AWS database IAM authentication.
*
* <p>Token is generated using IAM credential and region.
*
* <p>Implementation use SDK DefaultAWSCredentialsProviderChain and DefaultAwsRegionProviderChain
* (environment variable / system properties, files, ...) or using connection string options :
* accessKeyId, secretKey, region.
*
* @see <a
* href="https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.html">DefaultCredentialsProvider</a>
* @see <a
* href="https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/regions/providers/DefaultAwsRegionProviderChain.html">DefaultAwsRegionProviderChain</a>
*/
public class AwsIamCredentialPlugin implements CredentialPlugin {
private static final int TOKEN_TTL = 10;
private static final Map<KeyCache, IdentityExpire> cache = new ConcurrentHashMap<>();
private AwsCredentialGenerator generator;
private KeyCache key;
@Override
public String type() {
return "AWS-IAM";
}
@Override
public boolean mustUseSsl() {
return true;
}
@Override
public CredentialPlugin initialize(Configuration conf, String userName, HostAddress hostAddress)
throws SQLException {
try {
Class.forName("software.amazon.awssdk.auth.credentials.AwsBasicCredentials");
} catch (ClassNotFoundException ex) {
throw new SQLException(
"Identity plugin 'AWS-IAM' is used without having AWS SDK in "
+ "classpath. "
+ "Please add 'software.amazon.awssdk:rds' to classpath");
}
this.generator = new AwsCredentialGenerator(conf.nonMappedOptions(), conf.user(), hostAddress);
this.key = new KeyCache(conf, conf.user(), hostAddress);
return this;
}
@Override
public Credential get() {
IdentityExpire val = cache.get(key);
if (val != null && val.isValid()) {
return val.getCredential();
}
Credential credential = generator.getToken();
cache.put(key, new IdentityExpire(credential));
return credential;
}
private static class IdentityExpire {
private final LocalDateTime expiration;
private final Credential credential;
public IdentityExpire(Credential credential) {
this.credential = credential;
expiration = now().plusMinutes(TOKEN_TTL);
}
public boolean isValid() {
return expiration.isAfter(now());
}
public Credential getCredential() {
return credential;
}
}
private static class KeyCache {
private final Configuration conf;
private final String userName;
private final HostAddress hostAddress;
public KeyCache(Configuration conf, String userName, HostAddress hostAddress) {
this.conf = conf;
this.userName = userName;
this.hostAddress = hostAddress;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
KeyCache keyCache = (KeyCache) o;
return conf.equals(keyCache.conf)
&& Objects.equals(userName, keyCache.userName)
&& hostAddress.equals(keyCache.hostAddress);
}
@Override
public int hashCode() {
return Objects.hash(conf, userName, hostAddress);
}
}
}