-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
executable file
·212 lines (187 loc) · 8.37 KB
/
main.py
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
# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
# except in compliance with the License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
"""
Remove a node from Chef server when a termination event is received
Extended by [email protected] for multiple chef organizations and to clean up
AD, DNS, spacewalk, solarwinds, chef Automate.
Please follow instructions found at https://aws.amazon.com/blogs/apn/automatically-delete-terminated-instances-in-chef-server-with-aws-lambda-and-cloudwatch-events/
or the original github repo found at https://github.com/awslabs/lambda-chef-node-cleanup/blob/master/lambda/main.py
v3.0.0
"""
from __future__ import print_function
import logging
# only needed to using self signed certificate as noted below on line 52
import os
from base64 import b64decode
from botocore.exceptions import ClientError
import boto3
from chef import ChefAPI, Node, Search, Client
from chef.exceptions import ChefServerNotFoundError
import paramiko
import winrm
import xmlrpclib
def decrypt(file_name):
"""Decrypt the Ciphertext Blob to get USERNAME's pem or password"""
try:
with open(file_name, 'r') as encrypted_file:
hash_file = encrypted_file.read()
kms = boto3.client('kms')
return kms.decrypt(CiphertextBlob=b64decode(hash_file))['Plaintext']
except (IOError, ClientError, KeyError) as err:
LOGGER.error(err)
return False
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)
CHEF_SERVER_URLS = (
'https://chef-automate-server.example.com/organizations/default',
'https://chef-automate-server.example.com/organizations/corey-dev',
'https://chef-automate-server.examle.com/organizations/dan-dev'
)
CHEF_USERNAME = 'user_with_admin_privs_for_org'
CHEF_PEM = decrypt('encrypted_chef_pem.txt')
# Needed if using self signed certs such as when using a test Chef Server.
# Include the certificate in the Lambda package at the location specified.
os.environ["SSL_CERT_FILE"] = "opsworks-cm-ca-2016-root.pem"
SATELLITE_URL = "http://spacewalk.example.com/rpc/api"
SATELLITE_LOGIN = 'username'
SATELLITE_PASSWORD = decrypt('encrypted_spacewalk_password.txt')
AD_USER = 'user_with_admin_privs_for_AD'
AD_PASSWORD = decrypt('encrypted_AD_password.txt')
AD_SERVER = 'activedirectory-server.example.com'
SW_SERVER = 'solarwinds.example.com'
SW_USER = 'user_with_host_login_and_admin_privs_to_solarwinds'
SW_PASSWORD = decrypt('encrypted_SW_password.txt')
sshhostname = 'chef-automate-server.example.com'
sshuser = 'ec2-user'
sshkey = 'aws_ssh_key_pem.txt'
def chef_automate_cleanup(machine_name):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(sshhostname, username=sshuser, key_filename=sshkey)
try:
stdin, stdout, stderr = ssh.exec_command('automate-ctl delete-visibility-node {}'.format(machine_name))
LOGGER.info('=====SUCCESSFULLY REMOVED INSTANCE {} FROM Chef Automate===== '.format(machine_name))
LOGGER.info(stdout.readlines())
except Exception as err:
LOGGER.error(err)
ssh.close()
def active_directory_cleanup(machine_name):
if machine_name:
ps_script = """Remove-ADComputer -Identity "{}" -confirm:$false""".format(machine_name)
s = winrm.Session(AD_SERVER, auth=(AD_USER, AD_PASSWORD))
try:
r = s.run_ps(ps_script)
LOGGER.info('=====SUCCESSFULLY REMOVED INSTANCE {} FROM AD===== '.format(machine_name))
except r.std_err as err:
LOGGER.error(err)
def dns_cleanup(machine_name):
if machine_name:
ps_script = """$NodeToDelete = "{}"
$DNSServer = "{}"
$ZoneName = "teamfreeze.com"
$NodeDNS = $null
$NodeDNS = Get-DnsServerResourceRecord -ZoneName $ZoneName -ComputerName $DNSServer -Node $NodeToDelete -RRType A -ErrorAction SilentlyContinue
if($NodeDNS -eq $null){{
Write-Host "No DNS record found"
}}
else {{
Remove-DnsServerResourceRecord -ZoneName $ZoneName -ComputerName $DNSServer -InputObject $NodeDNS -Force
}} """.format(machine_name, AD_SERVER)
s = winrm.Session(AD_SERVER, auth=(AD_USER, AD_PASSWORD))
try:
r = s.run_ps(ps_script)
LOGGER.info('=====SUCCESSFULLY REMOVED INSTANCE {} FROM DNS===== '.format(machine_name))
except r.std_err as err:
LOGGER.error(err)
def solarwinds_cleanup(machine_ip, machine_name):
if machine_name:
ps_script = """Add-PSSnapin SwisSnapin
$Username = '{}'
$Password = ConvertTo-SecureString -String '{}' -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $Password
$ORIONSERVERNAME = '{}'
$nodeIP = '{}'
$swis = Connect-Swis -Credential $cred -host $orionservername
$nodeuri = Get-SwisData $swis "SELECT uri FROM Orion.Nodes WHERE IP LIKE '$nodeIP'"
Remove-SwisObject $swis -Uri $nodeuri """.format(SW_USER, SW_PASSWORD, SW_SERVER, machine_ip)
s = winrm.Session(AD_SERVER, auth=(AD_USER, AD_PASSWORD))
try:
s.run_ps(ps_script)
LOGGER.info('=====SUCCESSFULLY REMOVED INSTANCE {} FROM SolarWinds===== '.format(machine_name))
except Exception as err:
LOGGER.error(err)
def spacewalk_cleanup(machine_ip):
if machine_ip:
client = xmlrpclib.Server(SATELLITE_URL, verbose=0)
key = client.auth.login(SATELLITE_LOGIN, SATELLITE_PASSWORD)
list = client.system.search.ip(key, machine_ip)
for system in list:
name = system.get('name')
id = system.get('id')
try:
client.system.deleteSystem(key, id)
LOGGER.info('=====SUCCESSFULLY REMOVED INSTANCE {} FROM Spacewalk Server===== '.format(name))
except Exception as err:
LOGGER.error(err)
client.auth.logout(key)
def log_event(event):
"""Logs event information for debugging"""
LOGGER.info("========================================================")
LOGGER.info(event)
LOGGER.info("========================================================")
def get_instance_id(event):
"""Parses InstanceID from the event dict and gets the FQDN from EC2 API"""
try:
return event['detail']['instance-id']
except KeyError as err:
LOGGER.error(err)
return False
def handle(event, _context):
"""Lambda Handler"""
log_event(event)
node_name = None
node_ip = None
# Remove from one of the chef servers
for URL in CHEF_SERVER_URLS:
with ChefAPI(URL, CHEF_PEM, CHEF_USERNAME):
instance_id = get_instance_id(event)
try:
search = Search('node', 'ec2_instance_id:' + instance_id)
except ChefServerNotFoundError as err:
LOGGER.error(err)
return False
if len(search) != 0:
for instance in search:
node_name = instance.object.name
node = Node(node_name)
node_ip = node['ipaddress']
client = Client(node_name)
try:
node.delete()
client.delete()
LOGGER.info('=====SUCCESSFULLY REMOVED INSTANCE FROM CHEF SERVER===== {}'.format(URL))
break
except ChefServerNotFoundError as err:
LOGGER.error(err)
return False
else:
LOGGER.info('===Instance does not appear to be Chef Server managed.=== {}'.format(URL))
# Remove from Spacewalk
spacewalk_cleanup(node_ip)
# Remove from DNS
dns_cleanup(node_name)
# Remove from AD
active_directory_cleanup(node_name)
# Remove fom Solarwinds
solarwinds_cleanup(node_ip, node_name)
# Remove from Chef Automate
chef_automate_cleanup(node_name)