summaryrefslogtreecommitdiff
path: root/cloud/amazon/iam_cert.py
blob: dbc4fcb476e0c4f91b3ea23c36062eef3fdbbd2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/usr/bin/python
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: iam_cert
short_description: Manage server certificates for use on ELBs and CloudFront
description:
     - Allows for the management of server certificates
version_added: "2.0"
options:
  name:
    description:
      - Name of certificate to add, update or remove.
    required: true
    aliases: []
  new_name:
    description:
      - When present, this will update the name of the cert with the value passed here.
    required: false
    aliases: []
  new_path:
    description:
      - When present, this will update the path of the cert with the value passed here.
    required: false
    aliases: []
  state:
    description:
      - Whether to create, delete certificate. When present is specified it will attempt to make an update if new_path or new_name is specified.
    required: true
    default: null
    choices: [ "present", "absent" ]
    aliases: []
  path:
    description:
      - When creating or updating, specify the desired path of the certificate
    required: false
    default: "/"
    aliases: []
  cert_chain:
    description:
      - The path to the CA certificate chain in PEM encoded format.
    required: false
    default: null
    aliases: []
  cert:
    description:
      - The path to the certificate body in PEM encoded format.
    required: false
    aliases: []
  key:
    description:
      - The path to the private key of the certificate in PEM encoded format.
  dup_ok:
    description:
      - By default the module will not upload a certificate that is already uploaded into AWS. If set to True, it will upload the certificate as long as the name is unique.
    required: false
    default: False
    aliases: []
  aws_secret_key:
    description:
      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used.
    required: false
    default: null
    aliases: [ 'ec2_secret_key', 'secret_key' ]
  aws_access_key:
    description:
      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
    required: false
    default: null
    aliases: [ 'ec2_access_key', 'access_key' ]


requirements: [ "boto" ]
author: Jonathan I. Davila
extends_documentation_fragment:
    - aws
    - ec2
'''

EXAMPLES = '''
# Basic server certificate upload
tasks:
- name: Upload Certificate
  iam_cert:
    name: very_ssl
    state: present
    cert: somecert.pem
    key: privcertkey
    cert_chain: myverytrustedchain

'''
import json
import sys
try:
    import boto
    import boto.iam
    import boto.ec2
    HAS_BOTO = True
except ImportError:
    HAS_BOTO = False

def boto_exception(err):
    '''generic error message handler'''
    if hasattr(err, 'error_message'):
        error = err.error_message
    elif hasattr(err, 'message'):
        error = err.message
    else:
        error = '%s: %s' % (Exception, err)

    return error

def cert_meta(iam, name):
    opath       = iam.get_server_certificate(name).get_server_certificate_result.\
                                                 server_certificate.\
                                                 server_certificate_metadata.\
                                                 path
    ocert       = iam.get_server_certificate(name).get_server_certificate_result.\
                                                 server_certificate.\
                                                 certificate_body
    ocert_id    = iam.get_server_certificate(name).get_server_certificate_result.\
                                                 server_certificate.\
                                                 server_certificate_metadata.\
                                                 server_certificate_id
    upload_date = iam.get_server_certificate(name).get_server_certificate_result.\
                                                 server_certificate.\
                                                 server_certificate_metadata.\
                                                 upload_date
    exp         = iam.get_server_certificate(name).get_server_certificate_result.\
                                                 server_certificate.\
                                                 server_certificate_metadata.\
                                                 expiration
    return opath, ocert, ocert_id, upload_date, exp

def dup_check(module, iam, name, new_name, cert, orig_cert_names, orig_cert_bodies, dup_ok):
    update=False
    if any(ct in orig_cert_names for ct in [name, new_name]):
        for i_name in [name, new_name]:
            if i_name is None:
                continue

            if cert is not None:
                try:
                    c_index=orig_cert_names.index(i_name)
                except NameError:
                    continue
                else:
                    if orig_cert_bodies[c_index] == cert:
                        update=True
                        break
                    elif orig_cert_bodies[c_index] != cert:
                        module.fail_json(changed=False, msg='A cert with the name %s already exists and'
                                                           ' has a different certificate body associated'
                                                           ' with it. Certificates cannot have the same name' % i_name)
            else:
                update=True
                break
    elif cert in orig_cert_bodies and not dup_ok:
        for crt_name, crt_body in zip(orig_cert_names, orig_cert_bodies):
            if crt_body == cert:
                module.fail_json(changed=False, msg='This certificate already'
                                                    ' exists under the name %s' % crt_name)

    return update


def cert_action(module, iam, name, cpath, new_name, new_path, state,
                cert, key, chain, orig_cert_names, orig_cert_bodies, dup_ok):
    if state == 'present':
        update = dup_check(module, iam, name, new_name, cert, orig_cert_names,
                           orig_cert_bodies, dup_ok)
        if update:
            opath, ocert, ocert_id, upload_date, exp = cert_meta(iam, name)
            changed=True
            if new_name and new_path:
                iam.update_server_cert(name, new_cert_name=new_name, new_path=new_path)
                module.exit_json(changed=changed, original_name=name, new_name=new_name,
                                 original_path=opath, new_path=new_path, cert_body=ocert,
                                 upload_date=upload_date, expiration_date=exp)
            elif new_name and not new_path:
                iam.update_server_cert(name, new_cert_name=new_name)
                module.exit_json(changed=changed, original_name=name, new_name=new_name,
                                 cert_path=opath, cert_body=ocert,
                                 upload_date=upload_date, expiration_date=exp)
            elif not new_name and new_path:
                iam.update_server_cert(name, new_path=new_path)
                module.exit_json(changed=changed, name=new_name,
                                 original_path=opath, new_path=new_path, cert_body=ocert,
                                 upload_date=upload_date, expiration_date=exp)
            else:
                changed=False
                module.exit_json(changed=changed, name=name, cert_path=opath, cert_body=ocert,
                                 upload_date=upload_date, expiration_date=exp,
                                 msg='No new path or name specified. No changes made')
        else:
            changed=True
            iam.upload_server_cert(name, cert, key, cert_chain=chain, path=cpath)
            opath, ocert, ocert_id, upload_date, exp = cert_meta(iam, name)
            module.exit_json(changed=changed, name=name, cert_path=opath, cert_body=ocert,
                                 upload_date=upload_date, expiration_date=exp)
    elif state == 'absent':
        if name in orig_cert_names:
            changed=True
            iam.delete_server_cert(name)
            module.exit_json(changed=changed, deleted_cert=name)
        else:
            changed=False
            module.exit_json(changed=changed, msg='Certificate with the name %s already absent' % name)

def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(dict(
        state=dict(
            default=None, required=True, choices=['present', 'absent']),
        name=dict(default=None, required=False),
        cert=dict(default=None, required=False, type='path'),
        key=dict(default=None, required=False, type='path'),
        cert_chain=dict(default=None, required=False, type='path'),
        new_name=dict(default=None, required=False),
        path=dict(default='/', required=False),
        new_path=dict(default=None, required=False),
        dup_ok=dict(default=False, required=False, type='bool')
    )
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
        mutually_exclusive=[],
    )

    if not HAS_BOTO:
        module.fail_json(msg="Boto is required for this module")

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)

    try:
        if region:
            iam = connect_to_aws(boto.iam, region, **aws_connect_kwargs)
        else:
            iam = boto.iam.connection.IAMConnection(**aws_connect_kwargs)
    except boto.exception.NoAuthHandlerFound as e:
        module.fail_json(msg=str(e))

    state = module.params.get('state')
    name = module.params.get('name')
    path = module.params.get('path')
    new_name = module.params.get('new_name')
    new_path = module.params.get('new_path')
    cert_chain = module.params.get('cert_chain')
    dup_ok = module.params.get('dup_ok')
    if state == 'present':
        cert = open(module.params.get('cert'), 'r').read().rstrip()
        key = open(module.params.get('key'), 'r').read().rstrip()
        if cert_chain is not None:
            cert_chain = open(module.params.get('cert_chain'), 'r').read()
    else:
        key=cert=chain=None

    orig_certs = [ctb['server_certificate_name'] for ctb in \
                                                    iam.get_all_server_certs().\
                                                    list_server_certificates_result.\
                                                    server_certificate_metadata_list]
    orig_bodies = [iam.get_server_certificate(thing).\
                  get_server_certificate_result.\
                  certificate_body \
                  for thing in orig_certs]
    if new_name == name:
        new_name = None
    if new_path == path:
        new_path = None

    changed = False
    try:
        cert_action(module, iam, name, path, new_name, new_path, state,
                cert, key, cert_chain, orig_certs, orig_bodies, dup_ok)
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err), debug=[cert,key])


from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *

if __name__ == '__main__':
    main()