Repository: ansible/ansible

Issue:
amazon kinesis stream module to manage server side encryption
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
kinesis_stream_encryption

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.1.3 (meta-2.3 7d872b01b2) last updated 2017/09/12 14:37:04 (GMT +100)
  config file = 
  configured module search path = Default w/o overrides
  python version = 3.6.2 (default, Jul 17 2017, 16:44:45) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->

##### SUMMARY
<!--- Explain the problem briefly -->
As a kinesis stream user
I want to manage the serverside encryption on kinesis streams
So that the stream producers/consumers dont have to do encryption

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```

```

amazon kinesis stream module to manage server side encryption
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
kinesis_stream_encryption

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.1.3 (meta-2.3 7d872b01b2) last updated 2017/09/12 14:37:04 (GMT +100)
  config file = 
  configured module search path = Default w/o overrides
  python version = 3.6.2 (default, Jul 17 2017, 16:44:45) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->

##### SUMMARY
<!--- Explain the problem briefly -->
As a kinesis stream user
I want to manage the serverside encryption on kinesis streams
So that the stream producers/consumers dont have to do encryption

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```

```



Discussion:
cc @linuxdynasty
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @linuxdynasty
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/kinesis_stream.py b/lib/ansible/modules/cloud/amazon/kinesis_stream.py
--- a/lib/ansible/modules/cloud/amazon/kinesis_stream.py
+++ b/lib/ansible/modules/cloud/amazon/kinesis_stream.py
@@ -26,6 +26,7 @@
     - Create or Delete a Kinesis Stream.
     - Update the retention period of a Kinesis Stream.
     - Update Tags on a Kinesis Stream.
+    - Enable/disable server side encryption on a Kinesis Stream.
 version_added: "2.2"
 author: Allen Sanabria (@linuxdynasty)
 options:
@@ -69,6 +70,24 @@
     required: false
     default: null
     aliases: [ "resource_tags" ]
+  encryption_state:
+    description:
+      - "Enable or Disable encryption on the Kinesis Stream."
+    required: false
+    choices: [ 'enabled', 'disabled' ]
+    version_added: "2.5"
+  encryption_type:
+    description:
+      - "The type of encryption."
+    required: false
+    default: KMS
+    version_added: "2.5"
+  key_id:
+    description:
+      - "The GUID or alias for the KMS key."
+    required: false
+    default: None
+    version_added: "2.5"
 extends_documentation_fragment:
     - aws
     - ec2
@@ -117,6 +136,30 @@
     wait: yes
     wait_timeout: 600
   register: test_stream
+
+# Basic enable encryption example:
+- name: Encrypt Kinesis Stream test-stream.
+  kinesis_stream:
+    name: test-stream
+    state: present
+    encryption_state: enabled
+    encryption_type: KMS
+    key_id: alias/aws/kinesis
+    wait: yes
+    wait_timeout: 600
+  register: test_stream
+
+# Basic disable encryption example:
+- name: Encrypt Kinesis Stream test-stream.
+  kinesis_stream:
+    name: test-stream
+    state: present
+    encryption_state: disabled
+    encryption_type: KMS
+    key_id: alias/aws/kinesis
+    wait: yes
+    wait_timeout: 600
+  register: test_stream
 '''
 
 RETURN = '''
@@ -346,7 +389,8 @@ def find_stream(client, stream_name, check_mode=False):
                 'RetentionPeriodHours': 24,
                 'StreamName': stream_name,
                 'StreamARN': 'arn:aws:kinesis:east-side:123456789:stream/{0}'.format(stream_name),
-                'StreamStatus': 'ACTIVE'
+                'StreamStatus': 'ACTIVE',
+                'EncryptionType': 'NONE'
             }
         success = True
     except botocore.exceptions.ClientError as e:
@@ -637,6 +681,65 @@ def stream_action(client, stream_name, shard_count=1, action='create',
     return success, err_msg
 
 
+def stream_encryption_action(client, stream_name, action='start_encryption', encryption_type='', key_id='',
+                             timeout=300, check_mode=False):
+    """Create, Encrypt or Delete an Amazon Kinesis Stream.
+    Args:
+        client (botocore.client.EC2): Boto3 client.
+        stream_name (str): The name of the kinesis stream.
+
+    Kwargs:
+        shard_count (int): Number of shards this stream will use.
+        action (str): The action to perform.
+            valid actions == create and delete
+            default=create
+        encryption_type (str): NONE or KMS
+        key_id (str): The GUID or alias for the KMS key
+        check_mode (bool): This will pass DryRun as one of the parameters to the aws api.
+            default=False
+
+    Basic Usage:
+        >>> client = boto3.client('kinesis')
+        >>> stream_name = 'test-stream'
+        >>> shard_count = 20
+        >>> stream_action(client, stream_name, shard_count, action='create', encryption_type='KMS',key_id='alias/aws')
+
+    Returns:
+        List (bool, str)
+    """
+    success = False
+    err_msg = ''
+    params = {
+        'StreamName': stream_name
+    }
+    try:
+        if not check_mode:
+            if action == 'start_encryption':
+                params['EncryptionType'] = encryption_type
+                params['KeyId'] = key_id
+                client.start_stream_encryption(**params)
+                success = True
+            elif action == 'stop_encryption':
+                params['EncryptionType'] = encryption_type
+                params['KeyId'] = key_id
+                client.stop_stream_encryption(**params)
+                success = True
+            else:
+                err_msg = 'Invalid encryption action {0}'.format(action)
+        else:
+            if action == 'start_encryption':
+                success = True
+            elif action == 'stop_encryption':
+                success = True
+            else:
+                err_msg = 'Invalid encryption action {0}'.format(action)
+
+    except botocore.exceptions.ClientError as e:
+        err_msg = to_native(e)
+
+    return success, err_msg
+
+
 def retention_action(client, stream_name, retention_period=24,
                      action='increase', check_mode=False):
     """Increase or Decrease the retention of messages in the Kinesis stream.
@@ -674,16 +777,14 @@ def retention_action(client, stream_name, retention_period=24,
                 client.increase_stream_retention_period(**params)
                 success = True
                 err_msg = (
-                    'Retention Period increased successfully to {0}'
-                    .format(retention_period)
+                    'Retention Period increased successfully to {0}'.format(retention_period)
                 )
             elif action == 'decrease':
                 params['RetentionPeriodHours'] = retention_period
                 client.decrease_stream_retention_period(**params)
                 success = True
                 err_msg = (
-                    'Retention Period decreased successfully to {0}'
-                    .format(retention_period)
+                    'Retention Period decreased successfully to {0}'.format(retention_period)
                 )
             else:
                 err_msg = 'Invalid action {0}'.format(action)
@@ -1104,6 +1205,144 @@ def delete_stream(client, stream_name, wait=False, wait_timeout=300,
     return success, changed, err_msg, results
 
 
+def start_stream_encryption(client, stream_name, encryption_type='', key_id='',
+                            wait=False, wait_timeout=300, check_mode=False):
+    """Start encryption on an Amazon Kinesis Stream.
+    Args:
+        client (botocore.client.EC2): Boto3 client.
+        stream_name (str): The name of the kinesis stream.
+
+    Kwargs:
+        encryption_type (str): KMS or NONE
+        key_id (str): KMS key GUID or alias
+        wait (bool): Wait until Stream is ACTIVE.
+            default=False
+        wait_timeout (int): How long to wait until this operation is considered failed.
+            default=300
+        check_mode (bool): This will pass DryRun as one of the parameters to the aws api.
+            default=False
+
+    Basic Usage:
+        >>> client = boto3.client('kinesis')
+        >>> stream_name = 'test-stream'
+        >>> key_id = 'alias/aws'
+        >>> encryption_type = 'KMS'
+        >>> start_stream_encryption(client, stream_name,encryption_type,key_id)
+
+    Returns:
+        Tuple (bool, bool, str, dict)
+    """
+    success = False
+    changed = False
+    err_msg = ''
+    params = {
+        'StreamName': stream_name
+    }
+
+    results = dict()
+    stream_found, stream_msg, current_stream = (
+        find_stream(client, stream_name, check_mode=check_mode)
+    )
+    if stream_found:
+        success, err_msg = (
+            stream_encryption_action(
+                client, stream_name, action='start_encryption', encryption_type=encryption_type, key_id=key_id, check_mode=check_mode
+            )
+        )
+        if success:
+            changed = True
+            if wait:
+                success, err_msg, results = (
+                    wait_for_status(
+                        client, stream_name, 'ACTIVE', wait_timeout,
+                        check_mode=check_mode
+                    )
+                )
+                err_msg = 'Kinesis Stream {0} encryption started successfully.'.format(stream_name)
+                if not success:
+                    return success, True, err_msg, results
+            else:
+                err_msg = (
+                    'Kinesis Stream {0} is in the process of starting encryption.'.format(stream_name)
+                )
+    else:
+        success = True
+        changed = False
+        err_msg = 'Kinesis Stream {0} does not exist'.format(stream_name)
+
+    return success, changed, err_msg, results
+
+
+def stop_stream_encryption(client, stream_name, encryption_type='', key_id='',
+                           wait=True, wait_timeout=300, check_mode=False):
+    """Stop encryption on an Amazon Kinesis Stream.
+    Args:
+        client (botocore.client.EC2): Boto3 client.
+        stream_name (str): The name of the kinesis stream.
+
+    Kwargs:
+        encryption_type (str): KMS or NONE
+        key_id (str): KMS key GUID or alias
+        wait (bool): Wait until Stream is ACTIVE.
+            default=False
+        wait_timeout (int): How long to wait until this operation is considered failed.
+            default=300
+        check_mode (bool): This will pass DryRun as one of the parameters to the aws api.
+            default=False
+
+    Basic Usage:
+        >>> client = boto3.client('kinesis')
+        >>> stream_name = 'test-stream'
+        >>> start_stream_encryption(client, stream_name,encryption_type, key_id)
+
+    Returns:
+        Tuple (bool, bool, str, dict)
+    """
+    success = False
+    changed = False
+    err_msg = ''
+    params = {
+        'StreamName': stream_name
+    }
+
+    results = dict()
+    stream_found, stream_msg, current_stream = (
+        find_stream(client, stream_name, check_mode=check_mode)
+    )
+    if stream_found:
+        if current_stream.get('EncryptionType') == 'KMS':
+            success, err_msg = (
+                stream_encryption_action(
+                    client, stream_name, action='stop_encryption', key_id=key_id, encryption_type=encryption_type, check_mode=check_mode
+                )
+            )
+        elif current_stream.get('EncryptionType') == 'NONE':
+            success = True
+
+        if success:
+            changed = True
+            if wait:
+                success, err_msg, results = (
+                    wait_for_status(
+                        client, stream_name, 'ACTIVE', wait_timeout,
+                        check_mode=check_mode
+                    )
+                )
+                err_msg = 'Kinesis Stream {0} encryption stopped successfully.'.format(stream_name)
+                if not success:
+                    return success, True, err_msg, results
+            else:
+                err_msg = (
+                    'Stream {0} is in the process of stopping encryption.'.format(stream_name)
+                )
+    else:
+        success = True
+        changed = False
+        err_msg = 'Stream {0} does not exist.'.format(stream_name)
+
+    return success, changed, err_msg, results
+
+
 def main():
     argument_spec = ec2_argument_spec()
     argument_spec.update(
@@ -1115,6 +1354,9 @@ def main():
             wait=dict(default=True, required=False, type='bool'),
             wait_timeout=dict(default=300, required=False, type='int'),
             state=dict(default='present', choices=['present', 'absent']),
+            encryption_type=dict(required=False, choices=['NONE', 'KMS']),
+            key_id=dict(required=False, type='str'),
+            encryption_state=dict(required=False, choices=['enabled', 'disabled']),
         )
     )
     module = AnsibleModule(
@@ -1129,6 +1371,9 @@ def main():
     tags = module.params.get('tags')
     wait = module.params.get('wait')
     wait_timeout = module.params.get('wait_timeout')
+    encryption_type = module.params.get('encryption_type')
+    key_id = module.params.get('key_id')
+    encryption_state = module.params.get('encryption_state')
 
     if state == 'present' and not shards:
         module.fail_json(msg='Shards is required when state == present.')
@@ -1164,6 +1409,18 @@ def main():
                 wait, wait_timeout, check_mode
             )
         )
+        if encryption_state == 'enabled':
+            success, changed, err_msg, results = (
+                start_stream_encryption(
+                    client, stream_name, encryption_type, key_id, wait, wait_timeout, check_mode
+                )
+            )
+        elif encryption_state == 'disabled':
+            success, changed, err_msg, results = (
+                stop_stream_encryption(
+                    client, stream_name, encryption_type, key_id, wait, wait_timeout, check_mode
+                )
+            )
     elif state == 'absent':
         success, changed, err_msg, results = (
             delete_stream(client, stream_name, wait, wait_timeout, check_mode)


Test patch:
diff --git a/test/units/modules/cloud/amazon/test_kinesis_stream.py b/test/units/modules/cloud/amazon/test_kinesis_stream.py
--- a/test/units/modules/cloud/amazon/test_kinesis_stream.py
+++ b/test/units/modules/cloud/amazon/test_kinesis_stream.py
@@ -105,7 +105,8 @@ def test_find_stream(self):
             'RetentionPeriodHours': 24,
             'StreamName': 'test',
             'StreamARN': 'arn:aws:kinesis:east-side:123456789:stream/test',
-            'StreamStatus': 'ACTIVE'
+            'StreamStatus': 'ACTIVE',
+            'EncryptionType': 'NONE'
         }
         self.assertTrue(success)
         self.assertEqual(stream, should_return)
@@ -125,7 +126,8 @@ def test_wait_for_status(self):
             'RetentionPeriodHours': 24,
             'StreamName': 'test',
             'StreamARN': 'arn:aws:kinesis:east-side:123456789:stream/test',
-            'StreamStatus': 'ACTIVE'
+            'StreamStatus': 'ACTIVE',
+            'EncryptionType': 'NONE'
         }
         self.assertTrue(success)
         self.assertEqual(stream, should_return)
@@ -255,7 +257,8 @@ def test_update(self):
             'RetentionPeriodHours': 24,
             'StreamName': 'test',
             'StreamARN': 'arn:aws:kinesis:east-side:123456789:stream/test',
-            'StreamStatus': 'ACTIVE'
+            'StreamStatus': 'ACTIVE',
+            'EncryptionType': 'NONE'
         }
         tags = {
             'env': 'development',
@@ -292,9 +295,32 @@ def test_create_stream(self):
             'stream_name': 'test',
             'stream_arn': 'arn:aws:kinesis:east-side:123456789:stream/test',
             'stream_status': 'ACTIVE',
+            'encryption_type': 'NONE',
             'tags': tags,
         }
         self.assertTrue(success)
         self.assertTrue(changed)
         self.assertEqual(results, should_return)
         self.assertEqual(err_msg, 'Kinesis Stream test updated successfully.')
+
+    def test_enable_stream_encription(self):
+        client = boto3.client('kinesis', region_name=aws_region)
+        success, changed, err_msg, results = (
+            kinesis_stream.start_stream_encryption(
+                client, 'test', encryption_type='KMS', key_id='', wait=True, wait_timeout=60, check_mode=True
+            )
+        )
+        self.assertTrue(success)
+        self.assertTrue(changed)
+        self.assertEqual(err_msg, 'Kinesis Stream test encryption started successfully.')
+
+    def test_dsbale_stream_encryption(self):
+        client = boto3.client('kinesis', region_name=aws_region)
+        success, changed, err_msg, results = (
+            kinesis_stream.stop_stream_encryption(
+                client, 'test', encryption_type='KMS', key_id='', wait=True, wait_timeout=60, check_mode=True
+            )
+        )
+        self.assertTrue(success)
+        self.assertTrue(changed)
+        self.assertEqual(err_msg, 'Kinesis Stream test encryption stopped successfully.')