Repository: cloud-custodian/cloud-custodian

Issue:
attach-encrypt should support bucket sns notification



Discussion:
First step is adding sns support to mu.py
Alright, lessee here ...
I find `attach-encrypt` in [code](https://github.com/capitalone/cloud-custodian/blob/22101c4aa5c2c8171324c226fa7e0efe9a519890/c7n/resources/s3.py#L576-L670) but not in [docs](http://www.capitalone.io/cloud-custodian/docs/policy/resources/s3.html#actions) (fwiw).
I gather that the purpose of `attach-encrypt` is to install [a Lambda](https://github.com/capitalone/cloud-custodian/blob/22101c4aa5c2c8171324c226fa7e0efe9a519890/c7n/ufuncs/s3crypt.py#L42-L65) (I guess that's the "attach" part?) that [encrypts unencrypted s3 objects](https://github.com/capitalone/cloud-custodian/blob/22101c4aa5c2c8171324c226fa7e0efe9a519890/c7n/resources/s3.py#L957-L1173).
@kapilt So what's the tie-in to SNS? Are we publishing to SNS after encrypting? Or are we consuming from SNS as a trigger for encryption? My hunch is the former.
@whit537 - @kapilt can chime in, in case I'm missing something.  Just as the buckets can trigger lambdas, we want the ability to trigger SNS notifications.  In some edge cases, there are some buckets that already have lambdas attached for other valid reasons.  Since you can't have multiple lambda notifications on a single bucket, we want to trigger SNS instead.  From SNS, multiple lambdas can then be triggered.
consuming from sns, we already have publishing to sns.
> First step is adding sns support to mu.py

Starting a PR for this in https://github.com/capitalone/cloud-custodian/pull/869 ...
Alright, the way I'm interpreting this ticket is that we want a new mode type `sns` that would look like:

```yaml
policies:
  - name: s3-encryption
    resource: s3
    mode:
      type: sns
      topic: arn:aws:sns:region-0:123456789012:some-topic
    action: attach-encrypt
```

Is that the right track here, @kapilt?
for an initial pull request you can ignore the policy side, and just focus on mu.py support for a lambda event source on sns, there's several others there and then corresponding unit test in test_mu.py.

for a followup, the policy side here wouldn't be mode triggered, attach encrypt independently of the mode provisions a lambda on the bucket, and the logic would be in there depending on the extant bucket notification configuration on using a lambda notification or attaching to extant sns notification policies.
@kapilt So what's the end game here? Can you give me policy yaml or sample cli invocation demonstrating the final result we're going for here? Not to launch into full implementation but for context to understand the task on #869. Thanks.
for the use case around s3, the policy would just look like
```
policies:
  - name: s3-encryption
    resource: s3
    action: attach-encrypt
```

perhaps a higher bandwidth communication medium would be useful, let's try chat.
> provisions a lambda on the bucket, and the logic would be in there depending on the extant bucket notification configuration on using a lambda notification or attaching to extant sns notification policies.

/me reads up on http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html ...
> First step is adding sns support to mu.py

Ready in #869. Revisiting here ...
Benchmark: what happens when running the target policy currently?

```yaml
policies:
  - name: s3-encryption
    resource: s3
    action: attach-encrypt
```

```
(cloud-custodian) $ custodian run -c custodian.yml -s .
Traceback (most recent call last):
  File "/Users/whit537/workbench/capitalone/cloud-custodian/bin/custodian", line 11, in <module>
    load_entry_point('c7n', 'console_scripts', 'custodian')()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/cli.py", line 319, in main
    command(options)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/commands.py", line 44, in _load_policies
    policies = policy_load(options, options.config)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 62, in load
    raise Exception("Failed to validate on policy %s \n %s" % (errors[1], errors[0]))
Exception: Failed to validate on policy s3-encryption 
 Additional properties are not allowed ('action' was unexpected)

Failed validating 'additionalProperties' in schema[78]['allOf'][0]:
    {'additionalProperties': False,
     'properties': {'actions': {'type': 'array'},
                    'comment': {'type': 'string'},
                    'comments': {'type': 'string'},
                    'description': {'type': 'string'},
                    'filters': {'type': 'array'},
                    'max-resources': {'type': 'integer'},
                    'mode': {'$ref': '#/definitions/policy-mode'},
                    'name': {'pattern': '^[A-z][A-z0-9]*(-[A-z0-9]*[A-z][A-z0-9]*)*$',
                             'type': 'string'},
                    'query': {'items': {'maxProperties': 1,
                                        'minProperties': 1,
                                        'type': 'object'},
                              'type': 'array'},
                    'region': {'type': 'string'},
                    'resource': {'type': 'string'},
                    'source': {'enum': ['describe', 'config']},
                    'tags': {'items': {'type': 'string'},
                             'type': 'array'}},
     'required': ['name', 'resource'],
     'type': 'object'}

On instance:
    {'action': 'attach-encrypt', 'name': 's3-encryption', 'resource': 's3'}
(cloud-custodian) $
```
```yaml
policies:
  - name: s3-encryption
    resource: s3
    actions:
      - attach-encrypt
```

```
(cloud-custodian) $ custodian run -c custodian.yml -s .
Traceback (most recent call last):
  File "/Users/whit537/workbench/capitalone/cloud-custodian/bin/custodian", line 11, in <module>
    load_entry_point('c7n', 'console_scripts', 'custodian')()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/cli.py", line 319, in main
    command(options)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/commands.py", line 44, in _load_policies
    policies = policy_load(options, options.config)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 63, in load
    return PolicyCollection(data, options)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 76, in __init__
    Policy(p, options, session_factory=self.test_session_factory()))
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 489, in __init__
    self.resource_manager = self.get_resource_manager()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 586, in get_resource_manager
    return factory(self.ctx, self.data)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/resources/s3.py", line 92, in __init__
    super(S3, self).__init__(ctx, data)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/query.py", line 246, in __init__
    super(QueryResourceManager, self).__init__(data, options)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/manager.py", line 47, in __init__
    self.data.get('actions', []), self)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/actions.py", line 39, in parse
    results.append(self.factory(d, manager))
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/actions.py", line 58, in factory
    return action_class(data, manager).validate()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/resources/s3.py", line 612, in validate
    "attach-encrypt: role must be specified either "
ValueError: attach-encrypt: role must be specified either via assume or in config
(cloud-custodian) $
```
```yaml
policies:
  - name: s3-encryption
    resource: s3
    actions:
      - type: attach-encrypt
        role: arn:aws:iam::644160558196:role/custodian-mu
```

```
(cloud-custodian) $ custodian run -c custodian.yml -s .
2017-02-07 05:55:57,947: custodian.policy:INFO Running policy s3-encryption resource: s3 region:default c7n:0.8.22.0
2017-02-07 05:56:02,404: custodian.policy:INFO policy: s3-encryption resource:s3 has count:8 time:4.46
2017-02-07 05:56:04,289: custodian.lambda:INFO Publishing custodian policy lambda function c7n-s3-encrypt
2017-02-07 05:56:10,408: custodian.lambda:INFO Publishing custodian policy lambda function c7n-s3-encrypt
2017-02-07 05:56:17,033: custodian.s3:ERROR Error attaching lambda-encrypt An error occurred (InvalidArgument) when callin
g the PutBucketNotificationConfiguration operation: The notification destination service region is not valid for the bucke
t location constraint
None
2017-02-07 05:56:22,897: custodian.output:ERROR Error while executing policy
Traceback (most recent call last):
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 240, in run
    results = a.process(resources)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/resources/s3.py", line 665, in process
    results.append(f.result())
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/concurrent/futures/_base.py", line
 398, in result
    return self.__get_result()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/concurrent/futures/thread.py", lin
e 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/resources/s3.py", line 672, in process_bucket
    return source.add(func)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/mu.py", line 907, in add
    Bucket=self.bucket['Name'], NotificationConfiguration=notifies)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/botocore/client.py", line 251, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/botocore/client.py", line 537, in _make_api_call
    raise ClientError(parsed_response, operation_name)
ClientError: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: The notification destination service region is not valid for the bucket location constraint
2017-02-07 05:56:22,901: custodian.commands:ERROR Error while executing policy s3-encryption, continuing
Traceback (most recent call last):
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/commands.py", line 141, in run
    policy()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 572, in __call__
    return mode.run()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/policy.py", line 240, in run
    results = a.process(resources)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/resources/s3.py", line 665, in process
    results.append(f.result())
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/concurrent/futures/_base.py", line 398, in result
    return self.__get_result()
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/resources/s3.py", line 672, in process_bucket
    return source.add(func)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/c7n/mu.py", line 907, in add
    Bucket=self.bucket['Name'], NotificationConfiguration=notifies)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/botocore/client.py", line 251, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/whit537/workbench/capitalone/cloud-custodian/lib/python2.7/site-packages/botocore/client.py", line 537, in _make_api_call
    raise ClientError(parsed_response, operation_name)
ClientError: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: The notification destination service region is not valid for the bucket location constraint
(cloud-custodian) $
```
Success?

```yaml
policies:
  - name: s3-encryption
    resource: s3
    filters:
      - Name: whit537-297
    actions:
      - type: attach-encrypt
        role: arn:aws:iam::644160558196:role/custodian-mu
```

```
(cloud-custodian) $ custodian run -c custodian.yml -s . --region=us-west-2
2017-02-07 06:04:27,152: custodian.policy:INFO Running policy s3-encryption resource: s3 region:us-west-2 c7n:0.8.22.0
2017-02-07 06:04:27,153: custodian.policy:INFO policy: s3-encryption resource:s3 has count:1 time:0.00
2017-02-07 06:04:29,979: custodian.lambda:INFO Bucket lambda notification present
2017-02-07 06:04:30,038: custodian.policy:INFO policy: s3-encryption action: attachlambdaencrypt resources: 1 execution_time: 2.88
(cloud-custodian) $
```
Alright, so what did that actually do? And what do we want to be done differently?
> Bucket lambda notification present

My interpretation of https://github.com/capitalone/cloud-custodian/issues/297#issuecomment-275641802 is that we want to use an sns notification on the bucket instead of a lambda notification.
> attach-encrypt should support bucket sns notification

Alright, I'm pretty sure this means we want to support the new `SNSSubscription` from #869 as an event source in addition to [`BucketNotification` in `s3crypt`](https://github.com/capitalone/cloud-custodian/blob/22101c4aa5c2c8171324c226fa7e0efe9a519890/c7n/ufuncs/s3crypt.py#L81-L84).
Moving to PR in #910 ...
@whit537 pls can we chill on the blow blow, it spams every project watcher, who probably don't want to watch line by line policy construction and validation errors.
re what it did, it provisioned a lambda function that will encrypt objects as they are added to the bucket. it did so by setting a bucket notification. extant buckets may already have a bucket notification in which case this won't work, per s3 bucket notification capabilities. @ocampocj i believe was looking in the case of an sns notification on a bucket to attach the lambda to the topic.
D'oh! And here I thought I was leaving google-able traces for future confusees. 😜

I'll dial it back.
@kapilt Do you want automatic fallback to sns if a bucket notification lambda is already present? Or explicit `events` in the policy? Or both? Or ... ?

```yaml
policies:
  - name: s3-encryption
    resource: s3
    filters:
      - Name: whit537-297
    actions:
      - type: attach-encrypt
        role: arn:aws:iam::644160558196:role/custodian-mu
        events: []
```
+ @ocampocj   for confirm on behavior ... so i think this gets a bit gray quickly, i think the request came as can we look at the existing notifications on the bucket and for topics already extant by path, and attach ourselves to them. if there is no notification set on the bucket we either want to continue as before with a lambda notify or create an sns topic for the bucket and attach ourselves.

note the event afaicr is slightly different which means ufuncs/s3crypt will need to disambiguate between the two styles (direct bucket invoke of lambda, and relayed through sns).
@kapilt after looking through some of the data we have, this is clearly becoming much more of an outlier scenario.  from an implementation point of view, I think we should be able to scan for existing SNS notifications and attach the lambda or create a new SNS notification and attach the lambda if one doesn't exist.
>> if there is no notification set on the bucket we either want to continue as before with a lambda notify or create an sns topic for the bucket and attach ourselves.
>
> I think we should be able to scan for existing SNS notifications and attach the lambda or create a new SNS notification and attach the lambda if one doesn't exist.

Okay, this makes it sound like we are dropping bucket notifications entirely. Yes? _Only_ SNS?
sorry @whit537, I was trying to expand on @kapilt 's previous comment but it came out strangely.  under normal circumstances, we should continue with the status quo and attach the lambda.  that would be the 'default' behavior.  however, we should also be able to specify in a policy that we would like to attach the lambda via SNS, in which case, we should be able to attach to an existing SNS or create one in the event that one doesn't already exist.
> we should also be able to specify in a policy that we would like to attach the lambda via SNS

Any guidance on policy schema for this? Proposal:

```yaml
policies:
  - name: s3-encryption
    resource: s3
    filters:
      - Name: whit537-297
    actions:
      - type: attach-encrypt
        role: arn:aws:iam::644160558196:role/custodian-mu
        attach_via: 'sns'
```
> we should be able to attach to an existing SNS

And how do we pick the existing topic? Any topic with `s3:ObjectCreate:*`? A topic w/ an expected name? ... ?
example policy looks good.  though, if role is not specified, does it have a default?  role doesn't need to be specified when normally creating lambdas via policy, but is it needed for sns?  

I think the lambda should attach to any existing sns if the topic already has 's3:ObjectCreate:*'.  otherwise, I think the policy (or policies) would get pretty complex having to cite different sns topic names.
> role doesn't need to be specified when normally creating lambdas via policy, but is it needed for sns?

From what I can see, role _does_ need to be specified for normal lambda creation. It's the role the Lambda runs as, so it's necessary in either case (unless I'm missing something?).
> From what I can see

https://github.com/capitalone/cloud-custodian/issues/297#issuecomment-277964822
ok, makes sense.  it didn't look like it was a required field in the schema, but from your error it definitely seems so.

https://github.com/capitalone/cloud-custodian/blob/master/c7n/resources/s3.py#L639
> it didn't look like it was a required field in the schema

Ah, good point. Maybe I can work that into this PR ...
> Maybe I can work that into this PR ...

Actually, we already have a [test](https://github.com/capitalone/cloud-custodian/blob/9826826b1149e3b97fe806073ba67a75544c37e7/tests/test_s3.py#L615-L620) for the `ValueError` that results from not providing `role`. My hunch is that we don't enforce `role` in the schema because it can [also](https://github.com/capitalone/cloud-custodian/blob/9826826b1149e3b97fe806073ba67a75544c37e7/c7n/resources/s3.py#L654) be provided by `--assume`. 👍

Resolving patch:
diff --git a/c7n/mu.py b/c7n/mu.py
--- a/c7n/mu.py
+++ b/c7n/mu.py
@@ -825,8 +825,8 @@ def remove(self, func):
             self.client.delete_rule(Name=func.name)
 
 
-class BucketNotification(object):
-    """ Subscribe a lambda to bucket notifications. """
+class BucketLambdaNotification(object):
+    """ Subscribe a lambda to bucket notifications directly. """
 
     def __init__(self, data, session_factory, bucket):
         self.data = data
@@ -1058,6 +1058,49 @@ class Done(Exception): pass
                 pass
 
 
+class BucketSNSNotification(SNSSubscription):
+    """ Subscribe a lambda to bucket notifications via SNS. """
+
+    def __init__(self, session_factory, bucket, topic=None):
+        # NB: We are overwriting __init__ vs. extending.
+        self.session_factory = session_factory
+        self.session = session_factory()
+        self.topic_arns = self.get_topic(bucket) if topic is None else [topic]
+        self.client = self.session.client('sns')
+
+    def get_topic(self, bucket):
+        session = local_session(self.session_factory)
+        sns = session.client('sns')
+        s3 = session.client('s3')
+
+        notifies = bucket['Notification']
+        if 'TopicConfigurations' not in notifies:
+            notifies['TopicConfigurations'] = []
+        all_topics = notifies['TopicConfigurations']
+        topic_arns = [t['TopicArn'] for t in all_topics
+                      if 's3:ObjectCreated:*' in t['Events']]
+        if not topic_arns:
+            # No suitable existing topic. Create one.
+            topic_arn = sns.create_topic(Name=bucket['Name'])['TopicArn']
+            policy = {
+                'Statement': [{
+                    'Action': 'SNS:Publish',
+                    'Effect': 'Allow',
+                    'Resource': topic_arn,
+                    'Principal': {'Service': 's3.amazonaws.com'}}]}
+            sns.set_topic_attributes(
+                TopicArn=topic_arn,
+                AttributeName='Policy',
+                AttributeValue=json.dumps(policy))
+            notifies['TopicConfigurations'].append({
+                'TopicArn': topic_arn,
+                'Events': ['s3:ObjectCreated:*']})
+            s3.put_bucket_notification_configuration(Bucket=bucket['Name'],
+                NotificationConfiguration=notifies)
+            topic_arns = [topic_arn]
+        return topic_arns
+
+
 class ConfigRule(object):
     """Use a lambda as a custom config rule.
 
diff --git a/c7n/resources/s3.py b/c7n/resources/s3.py
--- a/c7n/resources/s3.py
+++ b/c7n/resources/s3.py
@@ -634,7 +634,7 @@ class AttachLambdaEncrypt(BucketActionBase):
                   - attach-encrypt
     """
     schema = type_schema(
-        'attach-encrypt', role={'type': 'string'})
+        'attach-encrypt', role={'type': 'string'}, topic={'type': 'string'})
 
     permissions = (
         "s3:PutBucketNotification", "s3:GetBucketNotification",
@@ -662,10 +662,11 @@ def process(self, buckets):
 
         session = local_session(self.manager.session_factory)
         account_id = self.manager.config.account_id
+        topic_arn = self.data.get('topic')
 
         func = get_function(
             None, self.data.get('role', self.manager.config.assume_role),
-            account_id=account_id)
+            bool(topic_arn), account_id=account_id)
 
         regions = set([
             b.get('Location', {
@@ -698,6 +699,7 @@ def process(self, buckets):
                         self.process_bucket,
                         region_funcs[region],
                         b,
+                        topic_arn,
                         account_id,
                         region_sessions[region]
                     ))
@@ -708,10 +710,14 @@ def process(self, buckets):
                 results.append(f.result())
             return filter(None, results)
 
-    def process_bucket(self, func, bucket, account_id, session_factory):
-        from c7n.mu import BucketNotification
-        source = BucketNotification(
-            {'account_s3': account_id}, session_factory, bucket)
+    def process_bucket(self, func, bucket, topic, account_id, session_factory):
+        from c7n.mu import BucketSNSNotification, BucketLambdaNotification
+        if topic:
+            topic = None if topic == 'default' else topic
+            source = BucketSNSNotification(session_factory, bucket, topic)
+        else:
+            source = BucketLambdaNotification(
+                {'account_s3': account_id}, session_factory, bucket)
         return source.add(func)
 
 
diff --git a/c7n/ufuncs/s3crypt.py b/c7n/ufuncs/s3crypt.py
--- a/c7n/ufuncs/s3crypt.py
+++ b/c7n/ufuncs/s3crypt.py
@@ -65,13 +65,18 @@ def process_key_event(event, context):
         print("remediated %s:%s" % (bucket, key['Key']))
 
 
-def get_function(session_factory, role, buckets=None, account_id=None):
+def process_sns_event(event, context):
+    for record in event.get('Records', []):
+        process_key_event(json.loads(record['Sns']['Message']), context)
+
+
+def get_function(session_factory, role, via_sns, buckets=None, account_id=None):
     from c7n.mu import (
-        LambdaFunction, custodian_archive, BucketNotification)
+        LambdaFunction, custodian_archive, BucketLambdaNotification)
 
     config = dict(
         name='c7n-s3-encrypt',
-        handler='s3crypt.process_key_event',
+        handler='s3crypt.process_' + 'sns_event' if via_sns else 'key_event',
         memory_size=256,
         timeout=30,
         role=role,
@@ -80,7 +85,8 @@ def get_function(session_factory, role, buckets=None, account_id=None):
 
     if buckets:
         config['events'] = [
-            BucketNotification({'account_s3': account_id}, session_factory, b)
+            BucketLambdaNotification({'account_s3': account_id},
+                session_factory, b)
             for b in buckets]
 
     archive = custodian_archive()


Test patch:
diff --git a/tests/common.py b/tests/common.py
--- a/tests/common.py
+++ b/tests/common.py
@@ -18,6 +18,7 @@
 import shutil
 import tempfile
 import yaml
+import unittest
 
 from c7n import policy
 from c7n.schema import generate, validate as schema_validate
@@ -38,6 +39,10 @@
 C7N_SCHEMA = generate()
 
 
+skip_if_not_validating = unittest.skipIf(
+    not C7N_VALIDATE, reason='We are not validating schemas.')
+
+
 class BaseTest(PillTest):
 
     def cleanUp(self):
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.ListBuckets_1.json b/tests/data/placebo/test_s3_attach_encrypt/s3.ListBuckets_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_s3_attach_encrypt/s3.ListBuckets_1.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "Owner": {
-            "DisplayName": "mandeep.bal", 
-            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
-        }, 
-        "Buckets": [
-            {
-                "CreationDate": {
-                    "hour": 16, 
-                    "__class__": "datetime", 
-                    "month": 10, 
-                    "second": 18, 
-                    "microsecond": 0, 
-                    "year": 2016, 
-                    "day": 15, 
-                    "minute": 0
-                }, 
-                "Name": "cf-templates-9c4kee3xbart-us-west-2"
-            }, 
-            {
-                "CreationDate": {
-                    "hour": 10, 
-                    "__class__": "datetime", 
-                    "month": 7, 
-                    "second": 14, 
-                    "microsecond": 0, 
-                    "year": 2016, 
-                    "day": 24, 
-                    "minute": 38
-                }, 
-                "Name": "config-bucket-644160558196"
-            }, 
-            {
-                "CreationDate": {
-                    "hour": 8, 
-                    "__class__": "datetime", 
-                    "month": 11, 
-                    "second": 7, 
-                    "microsecond": 0, 
-                    "year": 2016, 
-                    "day": 2, 
-                    "minute": 9
-                }, 
-                "Name": "custodian-attach-encrypt-test"
-            }, 
-            {
-                "CreationDate": {
-                    "hour": 8, 
-                    "__class__": "datetime", 
-                    "month": 7, 
-                    "second": 21, 
-                    "microsecond": 0, 
-                    "year": 2016, 
-                    "day": 24, 
-                    "minute": 50
-                }, 
-                "Name": "custodian-skunk-trails"
-            }
-        ], 
-        "ResponseMetadata": {
-            "HTTPStatusCode": 200, 
-            "RetryAttempts": 0, 
-            "HostId": "4X6++l8W3amv2gZ4r46vavRmvB2cD1cL16c8yu0Rs146+XltACsRaWNB6WwShDrHVSBDK6spLLQ=", 
-            "RequestId": "A394EB2241D7F2AA", 
-            "HTTPHeaders": {
-                "x-amz-id-2": "4X6++l8W3amv2gZ4r46vavRmvB2cD1cL16c8yu0Rs146+XltACsRaWNB6WwShDrHVSBDK6spLLQ=", 
-                "server": "AmazonS3", 
-                "transfer-encoding": "chunked", 
-                "x-amz-request-id": "A394EB2241D7F2AA", 
-                "date": "Wed, 02 Nov 2016 08:09:08 GMT", 
-                "content-type": "application/xml"
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt/iam.ListRoles_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/iam.ListRoles_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/iam.ListRoles_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/iam.ListRoles_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/lambda.AddPermission_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.AddPermission_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/lambda.AddPermission_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.AddPermission_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/lambda.CreateFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.CreateFunction_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/lambda.CreateFunction_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.CreateFunction_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/lambda.DeleteFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.DeleteFunction_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/lambda.DeleteFunction_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.DeleteFunction_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/lambda.GetFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.GetFunction_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/lambda.GetFunction_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/lambda.GetFunction_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.CreateBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.CreateBucket_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.CreateBucket_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.CreateBucket_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.DeleteBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.DeleteBucket_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.DeleteBucket_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.DeleteBucket_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.DeleteObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.DeleteObject_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.DeleteObject_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.DeleteObject_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_2.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_2.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_2.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_3.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_3.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_3.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_3.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_4.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_4.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketLocation_4.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketLocation_4.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketNotificationConfiguration_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketNotificationConfiguration_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketNotificationConfiguration_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketNotificationConfiguration_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketNotificationConfiguration_2.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.GetBucketNotificationConfiguration_2.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.GetBucketNotificationConfiguration_2.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.HeadObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.HeadObject_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.HeadObject_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.HeadObject_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.ListBuckets_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.ListBuckets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.ListBuckets_1.json
@@ -0,0 +1,38 @@
+{
+    "status_code": 200,
+    "data": {
+        "Owner": {
+            "DisplayName": "mandeep.bal",
+            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+        },
+        "Buckets": [
+            {
+                "CreationDate": {
+                    "hour": 8,
+                    "__class__": "datetime",
+                    "month": 11,
+                    "second": 7,
+                    "microsecond": 0,
+                    "year": 2016,
+                    "day": 2,
+                    "minute": 9
+                },
+                "Name": "custodian-attach-encrypt-test"
+            }
+        ],
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RetryAttempts": 0,
+            "HostId": "4X6++l8W3amv2gZ4r46vavRmvB2cD1cL16c8yu0Rs146+XltACsRaWNB6WwShDrHVSBDK6spLLQ=",
+            "RequestId": "A394EB2241D7F2AA",
+            "HTTPHeaders": {
+                "x-amz-id-2": "4X6++l8W3amv2gZ4r46vavRmvB2cD1cL16c8yu0Rs146+XltACsRaWNB6WwShDrHVSBDK6spLLQ=",
+                "server": "AmazonS3",
+                "transfer-encoding": "chunked",
+                "x-amz-request-id": "A394EB2241D7F2AA",
+                "date": "Wed, 02 Nov 2016 08:09:08 GMT",
+                "content-type": "application/xml"
+            }
+        }
+    }
+}
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.ListObjects_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.ListObjects_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.ListObjects_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.ListObjects_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.PutBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.PutBucketNotificationConfiguration_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.PutBucketNotificationConfiguration_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.PutBucketNotificationConfiguration_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt/s3.PutObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.PutObject_1.json
similarity index 100%
rename from tests/data/placebo/test_s3_attach_encrypt/s3.PutObject_1.json
rename to tests/data/placebo/test_s3_attach_encrypt_via_bucket_notification/s3.PutObject_1.json
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/iam.ListRoles_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/iam.ListRoles_1.json
@@ -0,0 +1,37 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AALS/S7WmFvs5ZjFIiX5HiCS9a7cB06ONBrMBDmHjyJU0lJ0dMdH0tycT/4bjkEKxqrLqhwR8tVoTFNxUiyp/HvdrZHdhE5QVjvq4TKl6VnyuQ==", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22apigateway.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAJ3IYRN5TVLQIYC5NU", 
+                "CreateDate": {
+                    "hour": 23, 
+                    "__class__": "datetime", 
+                    "month": 1, 
+                    "second": 42, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 9, 
+                    "minute": 9
+                }, 
+                "RoleName": "ApiGwLogger", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::644160558196:role/ApiGwLogger"
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "ce8523e4-0bd9-11e7-a03c-7b2455a28d8d", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "ce8523e4-0bd9-11e7-a03c-7b2455a28d8d", 
+                "date": "Sat, 18 Mar 2017 12:52:55 GMT", 
+                "content-length": "985", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.AddPermission_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.AddPermission_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.AddPermission_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 201, 
+    "data": {
+        "Statement": "{\"Sid\":\"sns-topic-preexisting-custodian-attach-encrypt-test\",\"Resource\":\"arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":[\"lambda:InvokeFunction\"],\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:sns:us-east-1:644160558196:preexisting-custodian-attach-encrypt-test\"}}}", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "d244c3c0-0bd9-11e7-b681-03ac90a0e430", 
+            "HTTPHeaders": {
+                "date": "Sat, 18 Mar 2017 12:53:01 GMT", 
+                "x-amzn-requestid": "d244c3c0-0bd9-11e7-b681-03ac90a0e430", 
+                "content-length": "399", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.CreateFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.CreateFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.CreateFunction_1.json
@@ -0,0 +1,29 @@
+{
+    "status_code": 201, 
+    "data": {
+        "CodeSha256": "D9aXUZxUbx/PebxVYD9jM6ZR2fKcDjGNqbGJ85NB2VE=", 
+        "FunctionName": "c7n-s3-encrypt", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "cf094749-0bd9-11e7-96e8-e9e870f8a1ce", 
+            "HTTPHeaders": {
+                "date": "Sat, 18 Mar 2017 12:53:01 GMT", 
+                "x-amzn-requestid": "cf094749-0bd9-11e7-96e8-e9e870f8a1ce", 
+                "content-length": "513", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }, 
+        "CodeSize": 2444839, 
+        "MemorySize": 256, 
+        "FunctionArn": "arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt", 
+        "Version": "124", 
+        "Role": "arn:aws:iam::644160558196:role/custodian-mu", 
+        "Timeout": 30, 
+        "LastModified": "2017-03-18T12:53:01.386+0000", 
+        "Handler": "s3crypt.process_sns_event", 
+        "Runtime": "python2.7", 
+        "Description": "Custodian S3 Key Encrypt"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.DeleteFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.DeleteFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.DeleteFunction_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 204, 
+            "RequestId": "ec809d68-0bd9-11e7-8056-d74c1d903356", 
+            "HTTPHeaders": {
+                "date": "Sat, 18 Mar 2017 12:53:46 GMT", 
+                "x-amzn-requestid": "ec809d68-0bd9-11e7-8056-d74c1d903356", 
+                "connection": "keep-alive", 
+                "content-type": "application/json"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.GetFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.GetFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/lambda.GetFunction_1.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 404, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 404, 
+            "RequestId": "cee8a077-0bd9-11e7-9c65-75be389e11b8", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "cee8a077-0bd9-11e7-9c65-75be389e11b8", 
+                "content-length": "109", 
+                "connection": "keep-alive", 
+                "date": "Sat, 18 Mar 2017 12:52:56 GMT", 
+                "content-type": "application/json", 
+                "x-amzn-errortype": "ResourceNotFoundException"
+            }
+        }, 
+        "Error": {
+            "Message": "Function not found: arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt", 
+            "Code": "ResourceNotFoundException"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/logs.DeleteLogGroup_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/logs.DeleteLogGroup_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/logs.DeleteLogGroup_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "ec639f63-0bd9-11e7-9f27-e569f4af8291", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "ec639f63-0bd9-11e7-9f27-e569f4af8291", 
+                "date": "Sat, 18 Mar 2017 12:53:45 GMT", 
+                "content-length": "0", 
+                "content-type": "application/x-amz-json-1.1"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.CreateBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.CreateBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.CreateBucket_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Location": "/custodian-attach-encrypt-test", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "joV4+Aq1WdxmgYguE/wcvTLASscyCfDjEEVvPSEVFwFx/vlvnvkJCRKBgY3ZPmKpidUqwKqu/zc=", 
+            "RequestId": "5A1A283801164956", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "joV4+Aq1WdxmgYguE/wcvTLASscyCfDjEEVvPSEVFwFx/vlvnvkJCRKBgY3ZPmKpidUqwKqu/zc=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "5A1A283801164956", 
+                "location": "/custodian-attach-encrypt-test", 
+                "date": "Sat, 18 Mar 2017 12:52:49 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.DeleteBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.DeleteBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.DeleteBucket_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "O48DSpXVKUpUv9zm/lz2qve2ORnVZhhRrjKVxDeMzuGS6ms3Z8W3IUCjppy7LmMk6x1JfOgI7uA=", 
+            "RequestId": "4E6B60691472DF76", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "O48DSpXVKUpUv9zm/lz2qve2ORnVZhhRrjKVxDeMzuGS6ms3Z8W3IUCjppy7LmMk6x1JfOgI7uA=", 
+                "date": "Sat, 18 Mar 2017 12:53:48 GMT", 
+                "x-amz-request-id": "4E6B60691472DF76", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.DeleteObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.DeleteObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.DeleteObject_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "o9vACNH5HHhLtcsad8Djtv7M6GJQAjR1VjgNiP485TaMfr0r1yJtZHv+gpEetJ5Og3GD9wQsMpo=", 
+            "RequestId": "B46AC472C4A19127", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "o9vACNH5HHhLtcsad8Djtv7M6GJQAjR1VjgNiP485TaMfr0r1yJtZHv+gpEetJ5Og3GD9wQsMpo=", 
+                "date": "Sat, 18 Mar 2017 12:53:48 GMT", 
+                "x-amz-request-id": "B46AC472C4A19127", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.GetBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.GetBucketNotificationConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.GetBucketNotificationConfiguration_1.json
@@ -0,0 +1,27 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "bIrRudYgs4HdstuWwQ6A+FEBcUxv7h2oHSpFwIqwAcQu+gZEUJI/qh6l+26GwDBTBx/j5gVxP3I=", 
+            "RequestId": "10C4B8C1780DE8CB", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "bIrRudYgs4HdstuWwQ6A+FEBcUxv7h2oHSpFwIqwAcQu+gZEUJI/qh6l+26GwDBTBx/j5gVxP3I=", 
+                "date": "Sat, 18 Mar 2017 12:52:54 GMT", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "10C4B8C1780DE8CB", 
+                "server": "AmazonS3"
+            }
+        }, 
+        "TopicConfigurations": [
+            {
+                "Id": "ZWZmYTk0ODItZTBkYi00MjIxLWI2N2ItODdjM2JhNWEwNWJh", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:preexisting-custodian-attach-encrypt-test", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.GetBucketNotificationConfiguration_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.GetBucketNotificationConfiguration_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.GetBucketNotificationConfiguration_2.json
@@ -0,0 +1,27 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "s3/uAjtePaaI7apvMG746Uo/zwpatlSFim+OlAajtNZwqD5zoSJNR+yni8SmXI6TpyM8s51kCqo=", 
+            "RequestId": "37F47E8F45011581", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "s3/uAjtePaaI7apvMG746Uo/zwpatlSFim+OlAajtNZwqD5zoSJNR+yni8SmXI6TpyM8s51kCqo=", 
+                "date": "Sat, 18 Mar 2017 12:53:15 GMT", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "37F47E8F45011581", 
+                "server": "AmazonS3"
+            }
+        }, 
+        "TopicConfigurations": [
+            {
+                "Id": "ZWZmYTk0ODItZTBkYi00MjIxLWI2N2ItODdjM2JhNWEwNWJh", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:preexisting-custodian-attach-encrypt-test", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.HeadObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.HeadObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.HeadObject_1.json
@@ -0,0 +1,39 @@
+{
+    "status_code": 200, 
+    "data": {
+        "AcceptRanges": "bytes", 
+        "ContentType": "text/plain", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "G6ojNbAx/3aqkv1xI9A3QGrvuhMp18IiKqxrRcTaktHYKBeVrIto/uc56nNgjR51SZkFNBz8X88=", 
+            "RequestId": "06D30EEFFB84F847", 
+            "HTTPHeaders": {
+                "content-length": "11", 
+                "x-amz-id-2": "G6ojNbAx/3aqkv1xI9A3QGrvuhMp18IiKqxrRcTaktHYKBeVrIto/uc56nNgjR51SZkFNBz8X88=", 
+                "accept-ranges": "bytes", 
+                "server": "AmazonS3", 
+                "last-modified": "Sat, 18 Mar 2017 12:53:17 GMT", 
+                "x-amz-request-id": "06D30EEFFB84F847", 
+                "etag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "date": "Sat, 18 Mar 2017 12:53:46 GMT", 
+                "x-amz-server-side-encryption": "AES256", 
+                "content-type": "text/plain"
+            }
+        }, 
+        "LastModified": {
+            "hour": 12, 
+            "__class__": "datetime", 
+            "month": 3, 
+            "second": 17, 
+            "microsecond": 0, 
+            "year": 2017, 
+            "day": 18, 
+            "minute": 53
+        }, 
+        "ContentLength": 11, 
+        "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+        "ServerSideEncryption": "AES256", 
+        "Metadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.ListBuckets_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.ListBuckets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.ListBuckets_1.json
@@ -0,0 +1,38 @@
+{
+    "status_code": 200,
+    "data": {
+        "Owner": {
+            "DisplayName": "mandeep.bal",
+            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+        },
+        "Buckets": [
+            {
+                "CreationDate": {
+                    "hour": 12,
+                    "__class__": "datetime",
+                    "month": 3,
+                    "second": 51,
+                    "microsecond": 0,
+                    "year": 2017,
+                    "day": 18,
+                    "minute": 52
+                },
+                "Name": "custodian-attach-encrypt-test"
+            }
+        ],
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RetryAttempts": 0,
+            "HostId": "nvvvdqhLLK3iaBroCwIQixyUsR5SGsYVq5bT63oF6kOx8ODSVXgKWEURQzytDVW6HoR7STGSesc=",
+            "RequestId": "4A064FBD4C2512C4",
+            "HTTPHeaders": {
+                "x-amz-id-2": "nvvvdqhLLK3iaBroCwIQixyUsR5SGsYVq5bT63oF6kOx8ODSVXgKWEURQzytDVW6HoR7STGSesc=",
+                "server": "AmazonS3",
+                "transfer-encoding": "chunked",
+                "x-amz-request-id": "4A064FBD4C2512C4",
+                "date": "Sat, 18 Mar 2017 12:52:52 GMT",
+                "content-type": "application/xml"
+            }
+        }
+    }
+}
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.ListObjects_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.ListObjects_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.ListObjects_1.json
@@ -0,0 +1,48 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Name": "custodian-attach-encrypt-test", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "g6OSFMdNpTiVU/zeQKZHcHYNWwJZV+KSPVNhWAudplZ5f16cS8F211AGAQBbgH5KUaYO0gRJVE8=", 
+            "RequestId": "572F2AA1F2A91C43", 
+            "HTTPHeaders": {
+                "x-amz-bucket-region": "us-east-1", 
+                "x-amz-id-2": "g6OSFMdNpTiVU/zeQKZHcHYNWwJZV+KSPVNhWAudplZ5f16cS8F211AGAQBbgH5KUaYO0gRJVE8=", 
+                "server": "AmazonS3", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "572F2AA1F2A91C43", 
+                "date": "Sat, 18 Mar 2017 12:53:47 GMT", 
+                "content-type": "application/xml"
+            }
+        }, 
+        "MaxKeys": 1000, 
+        "Prefix": "", 
+        "Marker": "", 
+        "EncodingType": "url", 
+        "IsTruncated": false, 
+        "Contents": [
+            {
+                "LastModified": {
+                    "hour": 12, 
+                    "__class__": "datetime", 
+                    "month": 3, 
+                    "second": 17, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 18, 
+                    "minute": 53
+                }, 
+                "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "StorageClass": "STANDARD", 
+                "Key": "hello-world.txt", 
+                "Owner": {
+                    "DisplayName": "mandeep.bal", 
+                    "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+                }, 
+                "Size": 11
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.PutBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.PutBucketNotificationConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.PutBucketNotificationConfiguration_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "pzAFIczHAeNYBhJmDT0zBD6qIf1StpNI0JzrzBRDM1xMkw6iNM+JXZEoxwpFXQBojwFgGTmIHqc=", 
+            "RequestId": "120ECF7F7D2ECF4F", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "pzAFIczHAeNYBhJmDT0zBD6qIf1StpNI0JzrzBRDM1xMkw6iNM+JXZEoxwpFXQBojwFgGTmIHqc=", 
+                "date": "Sat, 18 Mar 2017 12:52:50 GMT", 
+                "content-length": "0", 
+                "x-amz-request-id": "120ECF7F7D2ECF4F", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.PutObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.PutObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/s3.PutObject_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "N5y4XQP4gtOkaqP/Y/jhQet1BrJqRrKeiTW9d3tqfKc3OhUWfPKXYgS264a+pWnn5SOFf9w4z/g=", 
+            "RequestId": "DBF09D1CEF83AFAB", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "N5y4XQP4gtOkaqP/Y/jhQet1BrJqRrKeiTW9d3tqfKc3OhUWfPKXYgS264a+pWnn5SOFf9w4z/g=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "DBF09D1CEF83AFAB", 
+                "etag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "date": "Sat, 18 Mar 2017 12:53:15 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.CreateTopic_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.CreateTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.CreateTopic_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "23571191-7f56-5e07-b9e0-40d650b76ef3", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "23571191-7f56-5e07-b9e0-40d650b76ef3", 
+                "date": "Sat, 18 Mar 2017 12:52:48 GMT", 
+                "content-length": "348", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TopicArn": "arn:aws:sns:us-east-1:644160558196:preexisting-custodian-attach-encrypt-test"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.DeleteTopic_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.DeleteTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.DeleteTopic_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "4a82d362-5813-5b15-9476-82256809108b", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "4a82d362-5813-5b15-9476-82256809108b", 
+                "date": "Sat, 18 Mar 2017 12:53:46 GMT", 
+                "content-length": "201", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.SetTopicAttributes_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.SetTopicAttributes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.SetTopicAttributes_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "a2f37c63-dee0-5e7a-986c-2d24f1014e8e", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "a2f37c63-dee0-5e7a-986c-2d24f1014e8e", 
+                "date": "Sat, 18 Mar 2017 12:52:48 GMT", 
+                "content-length": "215", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.Subscribe_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.Subscribe_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_explicit_existing_topic/sns.Subscribe_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "7519d119-c8db-5d30-8d60-805ee734d24c", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "7519d119-c8db-5d30-8d60-805ee734d24c", 
+                "date": "Sat, 18 Mar 2017 12:53:03 GMT", 
+                "content-length": "391", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "SubscriptionArn": "arn:aws:sns:us-east-1:644160558196:preexisting-custodian-attach-encrypt-test:02d17e83-7518-4a8e-955c-2e71e837622b"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/iam.ListRoles_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/iam.ListRoles_1.json
@@ -0,0 +1,37 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "AAiJtPMY8duRNqO0rGx8Cmrc1IRCEKMOMmJny6xibp117DpAJ0XRWtqBa2DBD2Z1pqjWYP3aapDidzHaW3f+6T9O3LJO1yr1WzRXtJfNhNfsEg==", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22apigateway.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAJ3IYRN5TVLQIYC5NU", 
+                "CreateDate": {
+                    "hour": 23, 
+                    "__class__": "datetime", 
+                    "month": 1, 
+                    "second": 42, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 9, 
+                    "minute": 9
+                }, 
+                "RoleName": "ApiGwLogger", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::644160558196:role/ApiGwLogger"
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e9cd16fe-0a21-11e7-8649-d3eeaa03ac0e", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e9cd16fe-0a21-11e7-8649-d3eeaa03ac0e", 
+                "date": "Thu, 16 Mar 2017 08:24:02 GMT", 
+                "content-length": "985", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.AddPermission_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.AddPermission_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.AddPermission_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 201, 
+    "data": {
+        "Statement": "{\"Sid\":\"sns-topic-existing-custodian-attach-encrypt-test-jpg\",\"Resource\":\"arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":[\"lambda:InvokeFunction\"],\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-jpg\"}}}", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "edf1ba3e-0a21-11e7-bfd4-158977ff90c0", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:24:09 GMT", 
+                "x-amzn-requestid": "edf1ba3e-0a21-11e7-bfd4-158977ff90c0", 
+                "content-length": "401", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.AddPermission_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.AddPermission_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.AddPermission_2.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 201, 
+    "data": {
+        "Statement": "{\"Sid\":\"sns-topic-existing-custodian-attach-encrypt-test-txt\",\"Resource\":\"arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":[\"lambda:InvokeFunction\"],\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-txt\"}}}", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "ef29023f-0a21-11e7-8ecf-75228870aa64", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:24:11 GMT", 
+                "x-amzn-requestid": "ef29023f-0a21-11e7-8ecf-75228870aa64", 
+                "content-length": "401", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.CreateFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.CreateFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.CreateFunction_1.json
@@ -0,0 +1,29 @@
+{
+    "status_code": 201, 
+    "data": {
+        "CodeSha256": "10cSeyuOv/0QFpeKw9ZsIeCDOCLryBhULrGE/KiT13E=", 
+        "FunctionName": "c7n-s3-encrypt", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "ea670d5d-0a21-11e7-bbcd-350d373cb9f3", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:24:09 GMT", 
+                "x-amzn-requestid": "ea670d5d-0a21-11e7-bbcd-350d373cb9f3", 
+                "content-length": "513", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }, 
+        "CodeSize": 2445300, 
+        "MemorySize": 256, 
+        "FunctionArn": "arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt", 
+        "Version": "120", 
+        "Role": "arn:aws:iam::644160558196:role/custodian-mu", 
+        "Timeout": 30, 
+        "LastModified": "2017-03-16T08:24:09.145+0000", 
+        "Handler": "s3crypt.process_sns_event", 
+        "Runtime": "python2.7", 
+        "Description": "Custodian S3 Key Encrypt"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.DeleteFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.DeleteFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.DeleteFunction_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 204, 
+            "RequestId": "03b2c4e4-0a23-11e7-be09-31de23946115", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:31:55 GMT", 
+                "x-amzn-requestid": "03b2c4e4-0a23-11e7-be09-31de23946115", 
+                "connection": "keep-alive", 
+                "content-type": "application/json"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.GetFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.GetFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/lambda.GetFunction_1.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 404, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 404, 
+            "RequestId": "ea3bdebd-0a21-11e7-967a-835e56cc92d1", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "ea3bdebd-0a21-11e7-967a-835e56cc92d1", 
+                "content-length": "109", 
+                "connection": "keep-alive", 
+                "date": "Thu, 16 Mar 2017 08:24:03 GMT", 
+                "content-type": "application/json", 
+                "x-amzn-errortype": "ResourceNotFoundException"
+            }
+        }, 
+        "Error": {
+            "Message": "Function not found: arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt", 
+            "Code": "ResourceNotFoundException"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/logs.DeleteLogGroup_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/logs.DeleteLogGroup_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/logs.DeleteLogGroup_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "038cee04-0a23-11e7-bc4a-edfb9e528fe1", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "038cee04-0a23-11e7-bc4a-edfb9e528fe1", 
+                "date": "Thu, 16 Mar 2017 08:31:55 GMT", 
+                "content-length": "0", 
+                "content-type": "application/x-amz-json-1.1"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.CreateBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.CreateBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.CreateBucket_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Location": "/custodian-attach-encrypt-test", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "TYUSVua7xXSbpXKgfy8gGl7JSY/6ifbAIzWdzwjwM6JxGWSza/KHwFbp4c5NNQ5n4aN7HH3lMHk=", 
+            "RequestId": "D35F2E2BDEB5E8AA", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "TYUSVua7xXSbpXKgfy8gGl7JSY/6ifbAIzWdzwjwM6JxGWSza/KHwFbp4c5NNQ5n4aN7HH3lMHk=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "D35F2E2BDEB5E8AA", 
+                "location": "/custodian-attach-encrypt-test", 
+                "date": "Thu, 16 Mar 2017 08:23:53 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.DeleteBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.DeleteBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.DeleteBucket_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "E9Jymz3WdnUcCIhiaXr8ObrgGGmjrr1TDR1l+HVhnRpaPDARaxSlSsXt6bx4nwPttnBsFIYLmg8=", 
+            "RequestId": "DFEAF186B770C44C", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "E9Jymz3WdnUcCIhiaXr8ObrgGGmjrr1TDR1l+HVhnRpaPDARaxSlSsXt6bx4nwPttnBsFIYLmg8=", 
+                "date": "Thu, 16 Mar 2017 08:31:58 GMT", 
+                "x-amz-request-id": "DFEAF186B770C44C", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.DeleteObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.DeleteObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.DeleteObject_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "W/J/yCquRXB+bIHdyzBlVJwEQJQk7LHGbKBf0FjfHvXkgjd7KgY1h/y2dWrFx9YEm7EWSNIFKbI=", 
+            "RequestId": "C2E1E92725B36220", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "W/J/yCquRXB+bIHdyzBlVJwEQJQk7LHGbKBf0FjfHvXkgjd7KgY1h/y2dWrFx9YEm7EWSNIFKbI=", 
+                "date": "Thu, 16 Mar 2017 08:31:58 GMT", 
+                "x-amz-request-id": "C2E1E92725B36220", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.GetBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.GetBucketNotificationConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.GetBucketNotificationConfiguration_1.json
@@ -0,0 +1,54 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "kCFcXDPIASy5Afs5q/vIAOZfubxhmHB5lrJiz0X5fVFLBtdzWaPUAFT7gakrUS682vywcKZvErE=", 
+            "RequestId": "28687DEF05056480", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "kCFcXDPIASy5Afs5q/vIAOZfubxhmHB5lrJiz0X5fVFLBtdzWaPUAFT7gakrUS682vywcKZvErE=", 
+                "date": "Thu, 16 Mar 2017 08:23:59 GMT", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "28687DEF05056480", 
+                "server": "AmazonS3"
+            }
+        }, 
+        "TopicConfigurations": [
+            {
+                "Filter": {
+                    "Key": {
+                        "FilterRules": [
+                            {
+                                "Name": "Suffix", 
+                                "Value": ".jpg"
+                            }
+                        ]
+                    }
+                }, 
+                "Id": "YTMxMzRlMWMtNGU2Mi00NjVkLWFmNGUtZWJhNjhhZWZjNjQ5", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-jpg", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }, 
+            {
+                "Filter": {
+                    "Key": {
+                        "FilterRules": [
+                            {
+                                "Name": "Suffix", 
+                                "Value": ".txt"
+                            }
+                        ]
+                    }
+                }, 
+                "Id": "ZjZhMTAwMjgtZGIyNS00YTY5LThmY2EtOWZiM2IxYzcyZWYw", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-txt", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.GetBucketNotificationConfiguration_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.GetBucketNotificationConfiguration_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.GetBucketNotificationConfiguration_2.json
@@ -0,0 +1,54 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "G6072x0KT1aqTsk2rrK/JtlB9DBXy1od9yrVhJCAc1vVKOaWxUMwz2DNTscMMLrpofQD2XYGwag=", 
+            "RequestId": "A2468F163C3CE279", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "G6072x0KT1aqTsk2rrK/JtlB9DBXy1od9yrVhJCAc1vVKOaWxUMwz2DNTscMMLrpofQD2XYGwag=", 
+                "date": "Thu, 16 Mar 2017 08:24:25 GMT", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "A2468F163C3CE279", 
+                "server": "AmazonS3"
+            }
+        }, 
+        "TopicConfigurations": [
+            {
+                "Filter": {
+                    "Key": {
+                        "FilterRules": [
+                            {
+                                "Name": "Suffix", 
+                                "Value": ".jpg"
+                            }
+                        ]
+                    }
+                }, 
+                "Id": "YTMxMzRlMWMtNGU2Mi00NjVkLWFmNGUtZWJhNjhhZWZjNjQ5", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-jpg", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }, 
+            {
+                "Filter": {
+                    "Key": {
+                        "FilterRules": [
+                            {
+                                "Name": "Suffix", 
+                                "Value": ".txt"
+                            }
+                        ]
+                    }
+                }, 
+                "Id": "ZjZhMTAwMjgtZGIyNS00YTY5LThmY2EtOWZiM2IxYzcyZWYw", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-txt", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.HeadObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.HeadObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.HeadObject_1.json
@@ -0,0 +1,39 @@
+{
+    "status_code": 200, 
+    "data": {
+        "AcceptRanges": "bytes", 
+        "ContentType": "text/plain", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "wEEzHiRcpoFFW4aDht+vo0Vk4c9KiFFS2NF84Gv8wyVGOFWim983ZTIivlt/Qz1uOuFcJWZmgsM=", 
+            "RequestId": "1EC8DE1035E9E0C3", 
+            "HTTPHeaders": {
+                "content-length": "11", 
+                "x-amz-id-2": "wEEzHiRcpoFFW4aDht+vo0Vk4c9KiFFS2NF84Gv8wyVGOFWim983ZTIivlt/Qz1uOuFcJWZmgsM=", 
+                "accept-ranges": "bytes", 
+                "server": "AmazonS3", 
+                "last-modified": "Thu, 16 Mar 2017 08:24:26 GMT", 
+                "x-amz-request-id": "1EC8DE1035E9E0C3", 
+                "etag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "date": "Thu, 16 Mar 2017 08:24:56 GMT", 
+                "x-amz-server-side-encryption": "AES256", 
+                "content-type": "text/plain"
+            }
+        }, 
+        "LastModified": {
+            "hour": 8, 
+            "__class__": "datetime", 
+            "month": 3, 
+            "second": 26, 
+            "microsecond": 0, 
+            "year": 2017, 
+            "day": 16, 
+            "minute": 24
+        }, 
+        "ContentLength": 11, 
+        "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+        "ServerSideEncryption": "AES256", 
+        "Metadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.ListBuckets_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.ListBuckets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.ListBuckets_1.json
@@ -0,0 +1,38 @@
+{
+    "status_code": 200,
+    "data": {
+        "Owner": {
+            "DisplayName": "mandeep.bal",
+            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+        },
+        "Buckets": [
+            {
+                "CreationDate": {
+                    "hour": 8,
+                    "__class__": "datetime",
+                    "month": 3,
+                    "second": 56,
+                    "microsecond": 0,
+                    "year": 2017,
+                    "day": 16,
+                    "minute": 23
+                },
+                "Name": "custodian-attach-encrypt-test"
+            }
+        ],
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RetryAttempts": 0,
+            "HostId": "BaowWU2EnFoTvf0QokMDEU9eA89WvXu1+WdDFKHft16u+nCWZAQGYC/qLRgse3wi4MhzTyKZ3Ng=",
+            "RequestId": "C58C5948C4CF3504",
+            "HTTPHeaders": {
+                "x-amz-id-2": "BaowWU2EnFoTvf0QokMDEU9eA89WvXu1+WdDFKHft16u+nCWZAQGYC/qLRgse3wi4MhzTyKZ3Ng=",
+                "server": "AmazonS3",
+                "transfer-encoding": "chunked",
+                "x-amz-request-id": "C58C5948C4CF3504",
+                "date": "Thu, 16 Mar 2017 08:23:58 GMT",
+                "content-type": "application/xml"
+            }
+        }
+    }
+}
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.ListObjects_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.ListObjects_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.ListObjects_1.json
@@ -0,0 +1,48 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Name": "custodian-attach-encrypt-test", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "kXlXy4kcVof5JPgRxFGP258JtvBY/yNNMIlaN0zTjingKd3lQleiBLCXByFHy2A+1OP7PHVDViE=", 
+            "RequestId": "C1188D20AA74BFDA", 
+            "HTTPHeaders": {
+                "x-amz-bucket-region": "us-east-1", 
+                "x-amz-id-2": "kXlXy4kcVof5JPgRxFGP258JtvBY/yNNMIlaN0zTjingKd3lQleiBLCXByFHy2A+1OP7PHVDViE=", 
+                "server": "AmazonS3", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "C1188D20AA74BFDA", 
+                "date": "Thu, 16 Mar 2017 08:31:58 GMT", 
+                "content-type": "application/xml"
+            }
+        }, 
+        "MaxKeys": 1000, 
+        "Prefix": "", 
+        "Marker": "", 
+        "EncodingType": "url", 
+        "IsTruncated": false, 
+        "Contents": [
+            {
+                "LastModified": {
+                    "hour": 8, 
+                    "__class__": "datetime", 
+                    "month": 3, 
+                    "second": 26, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 16, 
+                    "minute": 24
+                }, 
+                "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "StorageClass": "STANDARD", 
+                "Key": "hello-world.txt", 
+                "Owner": {
+                    "DisplayName": "mandeep.bal", 
+                    "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+                }, 
+                "Size": 11
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.PutBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.PutBucketNotificationConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.PutBucketNotificationConfiguration_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "KZLYOI0Iy1T2HjTU/v68URghO0UfNqb1YDrPFvvmdWI9VPe9U/vftgrl+/F+Sfo0bsbocUKl4t0=", 
+            "RequestId": "04C88A2AA251C186", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "KZLYOI0Iy1T2HjTU/v68URghO0UfNqb1YDrPFvvmdWI9VPe9U/vftgrl+/F+Sfo0bsbocUKl4t0=", 
+                "date": "Thu, 16 Mar 2017 08:23:55 GMT", 
+                "content-length": "0", 
+                "x-amz-request-id": "04C88A2AA251C186", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.PutObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.PutObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/s3.PutObject_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "Rk6qQTs9qr2uqOq8BN7Mn6uvGfJp+oC4zI0cpYxcvFrz/k1WF2r5wQvX0ANrtYD88DSjw9YJ660=", 
+            "RequestId": "66D041F71A14D153", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "Rk6qQTs9qr2uqOq8BN7Mn6uvGfJp+oC4zI0cpYxcvFrz/k1WF2r5wQvX0ANrtYD88DSjw9YJ660=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "66D041F71A14D153", 
+                "etag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "date": "Thu, 16 Mar 2017 08:24:25 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.CreateTopic_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.CreateTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.CreateTopic_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "bfcb8836-3fd1-54fe-9b21-d9727208c4b4", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "bfcb8836-3fd1-54fe-9b21-d9727208c4b4", 
+                "date": "Thu, 16 Mar 2017 08:23:53 GMT", 
+                "content-length": "349", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TopicArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-jpg"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.CreateTopic_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.CreateTopic_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.CreateTopic_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "5d0e3b7a-52a6-547a-b82f-6a3e1f7c4995", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "5d0e3b7a-52a6-547a-b82f-6a3e1f7c4995", 
+                "date": "Thu, 16 Mar 2017 08:23:53 GMT", 
+                "content-length": "349", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TopicArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-txt"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.DeleteTopic_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.DeleteTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.DeleteTopic_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "edd9ed62-8164-5cab-a78e-bc26ced8ceba", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "edd9ed62-8164-5cab-a78e-bc26ced8ceba", 
+                "date": "Thu, 16 Mar 2017 08:31:55 GMT", 
+                "content-length": "201", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.DeleteTopic_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.DeleteTopic_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.DeleteTopic_2.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "e68f48da-c057-5a93-97a6-199e8feb4738", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e68f48da-c057-5a93-97a6-199e8feb4738", 
+                "date": "Thu, 16 Mar 2017 08:31:56 GMT", 
+                "content-length": "201", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.SetTopicAttributes_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.SetTopicAttributes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.SetTopicAttributes_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "f3c19c7f-6344-52b4-8a92-cd3b2e9d52fe", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "f3c19c7f-6344-52b4-8a92-cd3b2e9d52fe", 
+                "date": "Thu, 16 Mar 2017 08:23:53 GMT", 
+                "content-length": "215", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.SetTopicAttributes_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.SetTopicAttributes_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.SetTopicAttributes_2.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "4fe95343-9f74-579e-b9f3-0207d2ae7a71", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "4fe95343-9f74-579e-b9f3-0207d2ae7a71", 
+                "date": "Thu, 16 Mar 2017 08:23:53 GMT", 
+                "content-length": "215", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.Subscribe_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.Subscribe_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.Subscribe_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "919f595b-5775-576d-bcd2-081f1a9ed1a0", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "919f595b-5775-576d-bcd2-081f1a9ed1a0", 
+                "date": "Thu, 16 Mar 2017 08:24:11 GMT", 
+                "content-length": "392", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "SubscriptionArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-jpg:40cc0f56-8f74-477d-8ccf-eb000b15c72e"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.Subscribe_2.json b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.Subscribe_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_implicit_existing_topic/sns.Subscribe_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "88fef41d-3e9e-5e4a-a402-a559737342c0", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "88fef41d-3e9e-5e4a-a402-a559737342c0", 
+                "date": "Thu, 16 Mar 2017 08:24:13 GMT", 
+                "content-length": "392", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "SubscriptionArn": "arn:aws:sns:us-east-1:644160558196:existing-custodian-attach-encrypt-test-txt:8ca45a04-aeef-4b1d-bac0-696bec57d8b5"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/iam.ListRoles_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/iam.ListRoles_1.json
@@ -0,0 +1,37 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Marker": "ABVAANNWvZ+oWoinA4zseAs62hRwSkEUVEVxgn3C3gCItyJmsrjFbHI6X620Q1BjFYDN5U6obIQhTUZwMJKIVCrKkRQr0GBKVVVxLYM8SPFAKg==", 
+        "IsTruncated": true, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22apigateway.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAJ3IYRN5TVLQIYC5NU", 
+                "CreateDate": {
+                    "hour": 23, 
+                    "__class__": "datetime", 
+                    "month": 1, 
+                    "second": 42, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 9, 
+                    "minute": 9
+                }, 
+                "RoleName": "ApiGwLogger", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::644160558196:role/ApiGwLogger"
+            }
+        ], 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "68f6c297-0a21-11e7-a59a-ebdf573605c2", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "68f6c297-0a21-11e7-a59a-ebdf573605c2", 
+                "date": "Thu, 16 Mar 2017 08:20:25 GMT", 
+                "content-length": "985", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.AddPermission_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.AddPermission_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.AddPermission_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 201, 
+    "data": {
+        "Statement": "{\"Sid\":\"sns-topic-custodian-attach-encrypt-test\",\"Resource\":\"arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":[\"lambda:InvokeFunction\"],\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:sns:us-east-1:644160558196:custodian-attach-encrypt-test\"}}}", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "6e057844-0a21-11e7-8a29-719b152ac875", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:20:35 GMT", 
+                "x-amzn-requestid": "6e057844-0a21-11e7-8a29-719b152ac875", 
+                "content-length": "375", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.CreateFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.CreateFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.CreateFunction_1.json
@@ -0,0 +1,29 @@
+{
+    "status_code": 201, 
+    "data": {
+        "CodeSha256": "10cSeyuOv/0QFpeKw9ZsIeCDOCLryBhULrGE/KiT13E=", 
+        "FunctionName": "c7n-s3-encrypt", 
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 201, 
+            "RequestId": "6985bc27-0a21-11e7-9255-8380970ca227", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:20:32 GMT", 
+                "x-amzn-requestid": "6985bc27-0a21-11e7-9255-8380970ca227", 
+                "content-length": "513", 
+                "content-type": "application/json", 
+                "connection": "keep-alive"
+            }
+        }, 
+        "CodeSize": 2445300, 
+        "MemorySize": 256, 
+        "FunctionArn": "arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt", 
+        "Version": "119", 
+        "Role": "arn:aws:iam::644160558196:role/custodian-mu", 
+        "Timeout": 30, 
+        "LastModified": "2017-03-16T08:20:32.568+0000", 
+        "Handler": "s3crypt.process_sns_event", 
+        "Runtime": "python2.7", 
+        "Description": "Custodian S3 Key Encrypt"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.DeleteFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.DeleteFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.DeleteFunction_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 204, 
+            "RequestId": "b8ea234f-0a21-11e7-bf85-4d4d8e635dac", 
+            "HTTPHeaders": {
+                "date": "Thu, 16 Mar 2017 08:22:41 GMT", 
+                "x-amzn-requestid": "b8ea234f-0a21-11e7-bf85-4d4d8e635dac", 
+                "connection": "keep-alive", 
+                "content-type": "application/json"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.GetFunction_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.GetFunction_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/lambda.GetFunction_1.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 404, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 404, 
+            "RequestId": "6962cb24-0a21-11e7-ac7a-cbccc015f5ed", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "6962cb24-0a21-11e7-ac7a-cbccc015f5ed", 
+                "content-length": "109", 
+                "connection": "keep-alive", 
+                "date": "Thu, 16 Mar 2017 08:20:27 GMT", 
+                "content-type": "application/json", 
+                "x-amzn-errortype": "ResourceNotFoundException"
+            }
+        }, 
+        "Error": {
+            "Message": "Function not found: arn:aws:lambda:us-east-1:644160558196:function:c7n-s3-encrypt", 
+            "Code": "ResourceNotFoundException"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/logs.DeleteLogGroup_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/logs.DeleteLogGroup_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/logs.DeleteLogGroup_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "b84cd119-0a21-11e7-876e-f90bad973123", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b84cd119-0a21-11e7-876e-f90bad973123", 
+                "date": "Thu, 16 Mar 2017 08:22:39 GMT", 
+                "content-length": "0", 
+                "content-type": "application/x-amz-json-1.1"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.CreateBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.CreateBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.CreateBucket_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Location": "/custodian-attach-encrypt-test", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "RHyVOpCewc6K9Ck6Aq/JT33krocdtkAYTilOY3Nv66HlGQf8WySLk6AYBiichhejQayKqq3OLFg=", 
+            "RequestId": "224782A65853D1FF", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "RHyVOpCewc6K9Ck6Aq/JT33krocdtkAYTilOY3Nv66HlGQf8WySLk6AYBiichhejQayKqq3OLFg=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "224782A65853D1FF", 
+                "location": "/custodian-attach-encrypt-test", 
+                "date": "Thu, 16 Mar 2017 08:20:18 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.DeleteBucket_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.DeleteBucket_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.DeleteBucket_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "wfP5jS7UypAlTHu1Jn0Lwe/wQRB05C4pqSvEWsykB0RHKVx4cMAl04Af2o9jfzPZgLMTbN6MSOQ=", 
+            "RequestId": "017CA428305BC28E", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "wfP5jS7UypAlTHu1Jn0Lwe/wQRB05C4pqSvEWsykB0RHKVx4cMAl04Af2o9jfzPZgLMTbN6MSOQ=", 
+                "date": "Thu, 16 Mar 2017 08:22:42 GMT", 
+                "x-amz-request-id": "017CA428305BC28E", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.DeleteObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.DeleteObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.DeleteObject_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 204, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 204, 
+            "RetryAttempts": 0, 
+            "HostId": "K7/XwYhOGsUqvRS1tgXFdUJQr3DaQKf/czsMnB/e5lyUf8DJQKCtRC2SwZw6QwArO5ZOyAP3K1w=", 
+            "RequestId": "84F7F3603A8939B6", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "K7/XwYhOGsUqvRS1tgXFdUJQr3DaQKf/czsMnB/e5lyUf8DJQKCtRC2SwZw6QwArO5ZOyAP3K1w=", 
+                "date": "Thu, 16 Mar 2017 08:22:42 GMT", 
+                "x-amz-request-id": "84F7F3603A8939B6", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.GetBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.GetBucketNotificationConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.GetBucketNotificationConfiguration_1.json
@@ -0,0 +1,27 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "7YsvFHtTZf3FqfaQquDkG/IEizgbOr7qF/hPsxoiIWqnMm9TxONQ/BcYc8lemBtNljXbBXlB0+U=", 
+            "RequestId": "C235D015CA5A5A83", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "7YsvFHtTZf3FqfaQquDkG/IEizgbOr7qF/hPsxoiIWqnMm9TxONQ/BcYc8lemBtNljXbBXlB0+U=", 
+                "date": "Thu, 16 Mar 2017 08:20:49 GMT", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "C235D015CA5A5A83", 
+                "server": "AmazonS3"
+            }
+        }, 
+        "TopicConfigurations": [
+            {
+                "Id": "YzZjMzI3OGQtMzE4MC00YmI4LTlkNDEtOTM2MDVkZTE0OGI2", 
+                "TopicArn": "arn:aws:sns:us-east-1:644160558196:custodian-attach-encrypt-test", 
+                "Events": [
+                    "s3:ObjectCreated:*"
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.HeadObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.HeadObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.HeadObject_1.json
@@ -0,0 +1,39 @@
+{
+    "status_code": 200, 
+    "data": {
+        "AcceptRanges": "bytes", 
+        "ContentType": "text/plain", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "mmF4SgpAF6K/eJAlNCotays+pciZdtwA/f8MMlSgzncRIcQ43EPAzg5xjQdpCBNYv3IIEHHYcSU=", 
+            "RequestId": "BC675CAFEC9864D2", 
+            "HTTPHeaders": {
+                "content-length": "11", 
+                "x-amz-id-2": "mmF4SgpAF6K/eJAlNCotays+pciZdtwA/f8MMlSgzncRIcQ43EPAzg5xjQdpCBNYv3IIEHHYcSU=", 
+                "accept-ranges": "bytes", 
+                "server": "AmazonS3", 
+                "last-modified": "Thu, 16 Mar 2017 08:20:50 GMT", 
+                "x-amz-request-id": "BC675CAFEC9864D2", 
+                "etag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "date": "Thu, 16 Mar 2017 08:21:19 GMT", 
+                "x-amz-server-side-encryption": "AES256", 
+                "content-type": "text/plain"
+            }
+        }, 
+        "LastModified": {
+            "hour": 8, 
+            "__class__": "datetime", 
+            "month": 3, 
+            "second": 50, 
+            "microsecond": 0, 
+            "year": 2017, 
+            "day": 16, 
+            "minute": 20
+        }, 
+        "ContentLength": 11, 
+        "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+        "ServerSideEncryption": "AES256", 
+        "Metadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.ListBuckets_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.ListBuckets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.ListBuckets_1.json
@@ -0,0 +1,38 @@
+{
+    "status_code": 200,
+    "data": {
+        "Owner": {
+            "DisplayName": "mandeep.bal",
+            "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+        },
+        "Buckets": [
+            {
+                "CreationDate": {
+                    "hour": 8,
+                    "__class__": "datetime",
+                    "month": 3,
+                    "second": 19,
+                    "microsecond": 0,
+                    "year": 2017,
+                    "day": 16,
+                    "minute": 20
+                },
+                "Name": "custodian-attach-encrypt-test"
+            }
+        ],
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200,
+            "RetryAttempts": 0,
+            "HostId": "oJNCf6i/4QC62GYK6P5u+Jyi3VTltnVvqJoeH7JKn2XDBxmLtXTuUlbJwwl3/h9JQ+OxqUDKTrs=",
+            "RequestId": "D3C1E2AF30514ACA",
+            "HTTPHeaders": {
+                "x-amz-id-2": "oJNCf6i/4QC62GYK6P5u+Jyi3VTltnVvqJoeH7JKn2XDBxmLtXTuUlbJwwl3/h9JQ+OxqUDKTrs=",
+                "server": "AmazonS3",
+                "transfer-encoding": "chunked",
+                "x-amz-request-id": "D3C1E2AF30514ACA",
+                "date": "Thu, 16 Mar 2017 08:20:20 GMT",
+                "content-type": "application/xml"
+            }
+        }
+    }
+}
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.ListObjects_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.ListObjects_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.ListObjects_1.json
@@ -0,0 +1,48 @@
+{
+    "status_code": 200, 
+    "data": {
+        "Name": "custodian-attach-encrypt-test", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "bHIQ2BLqLpL22WLBvxradn6TUWHV+7OPb1vSElYJ6egHU2Recjg9B1ink07br42MYA3+5fn/hiI=", 
+            "RequestId": "10F85A11F22FEE54", 
+            "HTTPHeaders": {
+                "x-amz-bucket-region": "us-east-1", 
+                "x-amz-id-2": "bHIQ2BLqLpL22WLBvxradn6TUWHV+7OPb1vSElYJ6egHU2Recjg9B1ink07br42MYA3+5fn/hiI=", 
+                "server": "AmazonS3", 
+                "transfer-encoding": "chunked", 
+                "x-amz-request-id": "10F85A11F22FEE54", 
+                "date": "Thu, 16 Mar 2017 08:22:42 GMT", 
+                "content-type": "application/xml"
+            }
+        }, 
+        "MaxKeys": 1000, 
+        "Prefix": "", 
+        "Marker": "", 
+        "EncodingType": "url", 
+        "IsTruncated": false, 
+        "Contents": [
+            {
+                "LastModified": {
+                    "hour": 8, 
+                    "__class__": "datetime", 
+                    "month": 3, 
+                    "second": 50, 
+                    "microsecond": 0, 
+                    "year": 2017, 
+                    "day": 16, 
+                    "minute": 20
+                }, 
+                "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "StorageClass": "STANDARD", 
+                "Key": "hello-world.txt", 
+                "Owner": {
+                    "DisplayName": "mandeep.bal", 
+                    "ID": "e7c8bb65a5fc49cf906715eae09de9e4bb7861a96361ba79b833aa45f6833b15"
+                }, 
+                "Size": 11
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.PutBucketNotificationConfiguration_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.PutBucketNotificationConfiguration_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.PutBucketNotificationConfiguration_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "ih9pe04CDGYRVydOwuZMU5L4MkkoQ8T0E9M61EdQ/nK/wf2tfmdQJusoCPuc8BbNZWxMfqsQiMs=", 
+            "RequestId": "E41EEB125F8060AA", 
+            "HTTPHeaders": {
+                "x-amz-id-2": "ih9pe04CDGYRVydOwuZMU5L4MkkoQ8T0E9M61EdQ/nK/wf2tfmdQJusoCPuc8BbNZWxMfqsQiMs=", 
+                "date": "Thu, 16 Mar 2017 08:20:35 GMT", 
+                "content-length": "0", 
+                "x-amz-request-id": "E41EEB125F8060AA", 
+                "server": "AmazonS3"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.PutObject_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.PutObject_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/s3.PutObject_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ETag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RetryAttempts": 0, 
+            "HostId": "nqVhVgpnbGHV6U21sECqJvygC6uaJQdfrruEI2YLjw1olXS4I/55h/rR3RJCObKp3vy7ETwjAig=", 
+            "RequestId": "74B4BC3C68BC3C64", 
+            "HTTPHeaders": {
+                "content-length": "0", 
+                "x-amz-id-2": "nqVhVgpnbGHV6U21sECqJvygC6uaJQdfrruEI2YLjw1olXS4I/55h/rR3RJCObKp3vy7ETwjAig=", 
+                "server": "AmazonS3", 
+                "x-amz-request-id": "74B4BC3C68BC3C64", 
+                "etag": "\"5eb63bbbe01eeed093cb22bb8f5acdc3\"", 
+                "date": "Thu, 16 Mar 2017 08:20:49 GMT"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.CreateTopic_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.CreateTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.CreateTopic_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "2d6f8a1d-4ddf-5d7d-bb79-db5900ce14c5", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "2d6f8a1d-4ddf-5d7d-bb79-db5900ce14c5", 
+                "date": "Thu, 16 Mar 2017 08:20:33 GMT", 
+                "content-length": "336", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TopicArn": "arn:aws:sns:us-east-1:644160558196:custodian-attach-encrypt-test"
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.DeleteTopic_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.DeleteTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.DeleteTopic_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "0f9114a8-a2eb-563d-a8b2-45ecef7baba4", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "0f9114a8-a2eb-563d-a8b2-45ecef7baba4", 
+                "date": "Thu, 16 Mar 2017 08:22:40 GMT", 
+                "content-length": "201", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.SetTopicAttributes_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.SetTopicAttributes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.SetTopicAttributes_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "8124c117-e0c7-5c8e-8448-9053add68536", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "8124c117-e0c7-5c8e-8448-9053add68536", 
+                "date": "Thu, 16 Mar 2017 08:20:33 GMT", 
+                "content-length": "215", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.Subscribe_1.json b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.Subscribe_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_s3_attach_encrypt_via_new_topic/sns.Subscribe_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "1bbe035a-ef48-50b3-9138-521ea25d2465", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "1bbe035a-ef48-50b3-9138-521ea25d2465", 
+                "date": "Thu, 16 Mar 2017 08:20:37 GMT", 
+                "content-length": "379", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "SubscriptionArn": "arn:aws:sns:us-east-1:644160558196:custodian-attach-encrypt-test:494f4b65-ee42-4de2-85f6-4fe5e04b9293"
+    }
+}
\ No newline at end of file
diff --git a/tests/test_s3.py b/tests/test_s3.py
--- a/tests/test_s3.py
+++ b/tests/test_s3.py
@@ -27,7 +27,7 @@
 from c7n.mu import LambdaManager
 from c7n.ufuncs import s3crypt
 
-from common import BaseTest, event_data
+from common import BaseTest, event_data, skip_if_not_validating
 
 
 class RestoreCompletionTest(TestCase):
@@ -715,6 +715,15 @@ def test_attach_encrypt_requires_role(self):
              'resource': 's3',
              'actions': [{'type': 'attach-encrypt'}]})
 
+    @skip_if_not_validating
+    def test_attach_encrypt_accepts_topic(self):
+        p = self.load_policy(
+            {'name': 'attach-encrypt',
+             'resource': 's3',
+             'actions': [{
+                 'type': 'attach-encrypt', 'role': '-', 'topic': 'default'}]})
+        self.assertEqual(p.data['actions'][0]['topic'], 'default')
+
     def test_create_bucket_event(self):
         self.patch(s3, 'S3_AUGMENT_TABLE', [
             ('get_bucket_policy',  'Policy', None, 'Policy'),
@@ -762,11 +771,12 @@ def test_create_bucket_event(self):
                  u'Sid': u'RequireEncryptedPutObject'}],
              u'Version': u'2012-10-17'})
 
-    def test_attach_encrypt(self):
+    def test_attach_encrypt_via_bucket_notification(self):
         self.patch(s3, 'S3_AUGMENT_TABLE',
                    [('get_bucket_location', 'Location', None, None)])
         self.patch(s3.S3, 'executor_factory', MainThreadExecutor)
-        session_factory = self.replay_flight_data('test_s3_attach_encrypt')
+        session_factory = self.replay_flight_data(
+            'test_s3_attach_encrypt_via_bucket_notification')
         bname = "custodian-attach-encrypt-test"
         role = "arn:aws:iam::644160558196:role/custodian-mu"
         self.maxDiff = None
@@ -789,7 +799,7 @@ def test_attach_encrypt(self):
 
         self.addCleanup(
             LambdaManager(functools.partial(session_factory, region='us-west-2')).remove,
-            s3crypt.get_function(None, role))
+            s3crypt.get_function(None, role, False))
 
         resources = p.run()
         self.assertEqual(len(resources), 1)
@@ -802,7 +812,206 @@ def test_attach_encrypt(self):
             {'LambdaFunctionConfigurations': [{
                 'Events': ['s3:ObjectCreated:*'],
                 'Id': 'c7n-s3-encrypt',
-                'LambdaFunctionArn': 'arn:aws:lambda:us-west-2:644160558196:function:c7n-s3-encrypt'}]})
+                'LambdaFunctionArn':'arn:aws:lambda:us-west-2:644160558196:function:c7n-s3-encrypt'}]})
+        client.put_object(
+            Bucket=bname, Key='hello-world.txt',
+            Body='hello world', ContentType='text/plain')
+        #time.sleep(30)
+        info = client.head_object(Bucket=bname, Key='hello-world.txt')
+        self.assertTrue('ServerSideEncryption' in info)
+
+    def test_attach_encrypt_via_new_topic(self):
+        self.patch(s3, 'S3_AUGMENT_TABLE', [(
+            'get_bucket_notification_configuration', 'Notification', None,
+            None)])
+        self.patch(s3.S3, 'executor_factory', MainThreadExecutor)
+        session_factory = self.replay_flight_data(
+            'test_s3_attach_encrypt_via_new_topic')
+        bname = "custodian-attach-encrypt-test"
+        role = "arn:aws:iam::644160558196:role/custodian-mu"
+        self.maxDiff = None
+        session = session_factory(region='us-east-1')
+        client = session.client('s3')
+        client.create_bucket(Bucket=bname)
+        self.addCleanup(destroyBucket, client, bname)
+
+        p = self.load_policy({
+            'name': 'attach-encrypt',
+            'resource': 's3',
+            'filters': [{'Name': bname}],
+            'actions': [{
+                'type': 'attach-encrypt',
+                'role': role,
+                'topic': 'default'}]
+            }, session_factory=session_factory)
+
+        self.addCleanup(
+            LambdaManager(
+                functools.partial(session_factory, region='us-east-1')).remove,
+            s3crypt.get_function(None, role, True))
+        arn = 'arn:aws:sns:us-east-1:644160558196:custodian-attach-encrypt-test'
+        self.addCleanup(session.client('sns').delete_topic, TopicArn=arn)
+        self.addCleanup(session.client('logs').delete_log_group,
+            logGroupName='/aws/lambda/c7n-s3-encrypt')
+
+        # Check that the policy sets stuff up properly.
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        #time.sleep(10)
+        topic_notifications = client.get_bucket_notification_configuration(
+            Bucket=bname).get('TopicConfigurations', [])
+        us = [t for t in topic_notifications if t.get('TopicArn') == arn]
+        self.assertEqual(len(us), 1)
+
+        # Check that the stuff behaves properly.
+        client.put_object(
+            Bucket=bname, Key='hello-world.txt',
+            Body='hello world', ContentType='text/plain')
+        #time.sleep(30)
+        info = client.head_object(Bucket=bname, Key='hello-world.txt')
+        self.assertTrue('ServerSideEncryption' in info)
+
+    def test_attach_encrypt_via_implicit_existing_topic(self):
+        self.patch(s3, 'S3_AUGMENT_TABLE', [(
+            'get_bucket_notification_configuration', 'Notification', None,
+            None)])
+        self.patch(s3.S3, 'executor_factory', MainThreadExecutor)
+        session_factory = self.replay_flight_data(
+            'test_s3_attach_encrypt_via_implicit_existing_topic')
+        bname = "custodian-attach-encrypt-test"
+        role = "arn:aws:iam::644160558196:role/custodian-mu"
+        self.maxDiff = None
+        session = session_factory(region='us-east-1')
+        client = session.client('s3')
+        client.create_bucket(Bucket=bname)
+        self.addCleanup(destroyBucket, client, bname)
+
+        # Create two sns topics
+        topic_configs = []
+        for suffix in ('.jpg', '.txt'):
+            sns = session.client('sns')
+            existing_topic_arn = sns.create_topic(
+                Name='existing-{}-{}'.format(bname, suffix[1:]))['TopicArn']
+            policy = {
+                'Statement': [{
+                    'Action': 'SNS:Publish',
+                    'Effect': 'Allow',
+                    'Resource': existing_topic_arn,
+                    'Principal': {'Service': 's3.amazonaws.com'}}]}
+            sns.set_topic_attributes(
+                TopicArn=existing_topic_arn,
+                AttributeName='Policy',
+                AttributeValue=json.dumps(policy))
+            self.addCleanup(session.client('sns').delete_topic,
+                TopicArn=existing_topic_arn)
+            topic_configs.append({
+                'TopicArn': existing_topic_arn,
+                'Events': ['s3:ObjectCreated:*'],
+                'Filter': {'Key': {'FilterRules': [{
+                    'Name': 'suffix',
+                    'Value': suffix}]}}})
+        session.resource('s3').BucketNotification(bname).put(
+            NotificationConfiguration={'TopicConfigurations': topic_configs})
+
+        # Now define the policy.
+        p = self.load_policy({
+            'name': 'attach-encrypt',
+            'resource': 's3',
+            'filters': [{'Name': bname}],
+            'actions': [{
+                'type': 'attach-encrypt',
+                'role': role,
+                'topic': 'default'}]
+            }, session_factory=session_factory)
+        self.addCleanup(
+            LambdaManager(
+                functools.partial(session_factory, region='us-east-1')).remove,
+            s3crypt.get_function(None, role, True))
+        self.addCleanup(session.client('logs').delete_log_group,
+            logGroupName='/aws/lambda/c7n-s3-encrypt')
+
+        # Check that the policy sets stuff up properly.
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        #time.sleep(10)
+        notifies = client.get_bucket_notification_configuration(
+            Bucket=bname).get('TopicConfigurations', [])
+        existing = [t for t in notifies if 'existing' in t['TopicArn']]
+        self.assertEqual(len(existing), 2)
+
+        # Check that the stuff behaves properly.
+        client.put_object(
+            Bucket=bname, Key='hello-world.txt',
+            Body='hello world', ContentType='text/plain')
+        #time.sleep(30)
+        info = client.head_object(Bucket=bname, Key='hello-world.txt')
+        self.assertTrue('ServerSideEncryption' in info)
+
+    def test_attach_encrypt_via_explicit_existing_topic(self):
+        self.patch(s3, 'S3_AUGMENT_TABLE', [(
+            'get_bucket_notification_configuration', 'Notification', None,
+            None)])
+        self.patch(s3.S3, 'executor_factory', MainThreadExecutor)
+        session_factory = self.replay_flight_data(
+            'test_s3_attach_encrypt_via_explicit_existing_topic')
+        bname = "custodian-attach-encrypt-test"
+        role = "arn:aws:iam::644160558196:role/custodian-mu"
+        self.maxDiff = None
+        session = session_factory(region='us-east-1')
+        client = session.client('s3')
+        client.create_bucket(Bucket=bname)
+        self.addCleanup(destroyBucket, client, bname)
+
+        # Create an sns topic
+        topic_configs = []
+        sns = session.client('sns')
+        existing_topic_arn = sns.create_topic(
+            Name='preexisting-{}'.format(bname))['TopicArn']
+        policy = {
+            'Statement': [{
+                'Action': 'SNS:Publish',
+                'Effect': 'Allow',
+                'Resource': existing_topic_arn,
+                'Principal': {'Service': 's3.amazonaws.com'}}]}
+        sns.set_topic_attributes(
+            TopicArn=existing_topic_arn,
+            AttributeName='Policy',
+            AttributeValue=json.dumps(policy))
+        self.addCleanup(session.client('sns').delete_topic,
+            TopicArn=existing_topic_arn)
+        topic_configs.append({
+            'TopicArn': existing_topic_arn,
+            'Events': ['s3:ObjectCreated:*']})
+        session.resource('s3').BucketNotification(bname).put(
+            NotificationConfiguration={'TopicConfigurations': topic_configs})
+
+        # Now define the policy.
+        p = self.load_policy({
+            'name': 'attach-encrypt',
+            'resource': 's3',
+            'filters': [{'Name': bname}],
+            'actions': [{
+                'type': 'attach-encrypt',
+                'role': role,
+                'topic': existing_topic_arn}]
+            }, session_factory=session_factory)
+        self.addCleanup(
+            LambdaManager(
+                functools.partial(session_factory, region='us-east-1')).remove,
+            s3crypt.get_function(None, role, True))
+        self.addCleanup(session.client('logs').delete_log_group,
+            logGroupName='/aws/lambda/c7n-s3-encrypt')
+
+        # Check that the policy sets stuff up properly.
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        #time.sleep(10)
+        notifies = client.get_bucket_notification_configuration(
+            Bucket=bname).get('TopicConfigurations', [])
+        existing = [t for t in notifies if 'existing' in t['TopicArn']]
+        self.assertEqual(len(existing), 1)
+
+        # Check that the stuff behaves properly.
         client.put_object(
             Bucket=bname, Key='hello-world.txt',
             Body='hello world', ContentType='text/plain')