Added redshift.get_cluster_credentials (#3611)

* Added redshift.get_cluster_credentials

* Marked endpoint in list

* Removed f string from tests

* Python 2.7 compat changes

* Fixed parameter retrieval

* Formatting

* Removed try/catch in favor of if

* Changed to existing random_string util

Co-authored-by: Andrea Amorosi <aamorosi@amazon.es>
This commit is contained in:
Andrea Amorosi 2021-01-25 14:19:50 +01:00 committed by GitHub
parent d7f218bfec
commit 5a41866f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 148 additions and 2 deletions

View File

@ -6721,7 +6721,7 @@
## redshift ## redshift
<details> <details>
<summary>28% implemented</summary> <summary>29% implemented</summary>
- [ ] accept_reserved_node_exchange - [ ] accept_reserved_node_exchange
- [ ] authorize_cluster_security_group_ingress - [ ] authorize_cluster_security_group_ingress
@ -6789,7 +6789,7 @@
- [X] disable_snapshot_copy - [X] disable_snapshot_copy
- [ ] enable_logging - [ ] enable_logging
- [X] enable_snapshot_copy - [X] enable_snapshot_copy
- [ ] get_cluster_credentials - [X] get_cluster_credentials
- [ ] get_reserved_node_exchange_offerings - [ ] get_reserved_node_exchange_offerings
- [X] modify_cluster - [X] modify_cluster
- [ ] modify_cluster_db_revision - [ ] modify_cluster_db_revision

View File

@ -8,6 +8,7 @@ from boto3 import Session
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel, CloudFormationModel from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import iso_8601_datetime_with_milliseconds from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.utilities.utils import random_string
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from .exceptions import ( from .exceptions import (
ClusterAlreadyExistsFaultError, ClusterAlreadyExistsFaultError,
@ -941,6 +942,25 @@ class RedshiftBackend(BaseBackend):
resource = self._get_resource_from_arn(resource_name) resource = self._get_resource_from_arn(resource_name)
resource.delete_tags(tag_keys) resource.delete_tags(tag_keys)
def get_cluster_credentials(
self, cluster_identifier, db_user, auto_create, duration_seconds
):
if duration_seconds < 900 or duration_seconds > 3600:
raise InvalidParameterValueError(
"Token duration must be between 900 and 3600 seconds"
)
expiration = datetime.datetime.now() + datetime.timedelta(0, duration_seconds)
if cluster_identifier in self.clusters:
user_prefix = "IAM:" if auto_create is False else "IAMA:"
db_user = user_prefix + db_user
return {
"DbUser": db_user,
"DbPassword": random_string(32),
"Expiration": expiration,
}
else:
raise ClusterNotFoundError(cluster_identifier)
redshift_backends = {} redshift_backends = {}
for region in Session().get_available_regions("redshift"): for region in Session().get_available_regions("redshift"):

View File

@ -694,3 +694,24 @@ class RedshiftResponse(BaseResponse):
} }
} }
) )
def get_cluster_credentials(self):
cluster_identifier = self._get_param("ClusterIdentifier")
db_user = self._get_param("DbUser")
auto_create = self._get_bool_param("AutoCreate", False)
duration_seconds = self._get_int_param("DurationSeconds", 900)
cluster_credentials = self.redshift_backend.get_cluster_credentials(
cluster_identifier, db_user, auto_create, duration_seconds
)
return self.get_response(
{
"GetClusterCredentialsResponse": {
"GetClusterCredentialsResult": cluster_credentials,
"ResponseMetadata": {
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a"
},
}
}
)

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import time
import datetime import datetime
import boto import boto
@ -1487,3 +1488,107 @@ def test_resize_cluster():
) )
ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue") ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue")
ex.value.response["Error"]["Message"].should.contain("Invalid cluster type") ex.value.response["Error"]["Message"].should.contain("Invalid cluster type")
@mock_redshift
def test_get_cluster_credentials_non_existent_cluster():
client = boto3.client("redshift", region_name="us-east-1")
with pytest.raises(ClientError) as ex:
client.get_cluster_credentials(ClusterIdentifier="non-existent")
ex.value.response["Error"]["Code"].should.equal("ClusterNotFound")
ex.value.response["Error"]["Message"].should.match(r"Cluster .+ not found.")
@mock_redshift
def test_get_cluster_credentials_non_existent_cluster():
client = boto3.client("redshift", region_name="us-east-1")
with pytest.raises(ClientError) as ex:
client.get_cluster_credentials(
ClusterIdentifier="non-existent", DbUser="some_user"
)
ex.value.response["Error"]["Code"].should.equal("ClusterNotFound")
ex.value.response["Error"]["Message"].should.match(r"Cluster .+ not found.")
@mock_redshift
def test_get_cluster_credentials_invalid_duration():
client = boto3.client("redshift", region_name="us-east-1")
cluster_identifier = "my_cluster"
client.create_cluster(
ClusterIdentifier=cluster_identifier,
ClusterType="single-node",
DBName="test",
MasterUsername="user",
MasterUserPassword="password",
NodeType="ds2.xlarge",
)
db_user = "some_user"
with pytest.raises(ClientError) as ex:
client.get_cluster_credentials(
ClusterIdentifier=cluster_identifier, DbUser=db_user, DurationSeconds=899
)
ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue")
ex.value.response["Error"]["Message"].should.contain(
"Token duration must be between 900 and 3600 seconds"
)
with pytest.raises(ClientError) as ex:
client.get_cluster_credentials(
ClusterIdentifier=cluster_identifier, DbUser=db_user, DurationSeconds=3601
)
ex.value.response["Error"]["Code"].should.equal("InvalidParameterValue")
ex.value.response["Error"]["Message"].should.contain(
"Token duration must be between 900 and 3600 seconds"
)
@mock_redshift
def test_get_cluster_credentials():
client = boto3.client("redshift", region_name="us-east-1")
cluster_identifier = "my_cluster"
client.create_cluster(
ClusterIdentifier=cluster_identifier,
ClusterType="single-node",
DBName="test",
MasterUsername="user",
MasterUserPassword="password",
NodeType="ds2.xlarge",
)
expected_expiration = time.mktime(
(datetime.datetime.now() + datetime.timedelta(0, 900)).timetuple()
)
db_user = "some_user"
response = client.get_cluster_credentials(
ClusterIdentifier=cluster_identifier, DbUser=db_user,
)
response["DbUser"].should.equal("IAM:%s" % db_user)
assert time.mktime((response["Expiration"]).timetuple()) == pytest.approx(
expected_expiration
)
response["DbPassword"].should.have.length_of(32)
response = client.get_cluster_credentials(
ClusterIdentifier=cluster_identifier, DbUser=db_user, AutoCreate=True
)
response["DbUser"].should.equal("IAMA:%s" % db_user)
response = client.get_cluster_credentials(
ClusterIdentifier=cluster_identifier, DbUser="some_other_user", AutoCreate=False
)
response["DbUser"].should.equal("IAM:%s" % "some_other_user")
expected_expiration = time.mktime(
(datetime.datetime.now() + datetime.timedelta(0, 3000)).timetuple()
)
response = client.get_cluster_credentials(
ClusterIdentifier=cluster_identifier, DbUser=db_user, DurationSeconds=3000,
)
assert time.mktime(response["Expiration"].timetuple()) == pytest.approx(
expected_expiration
)