View Javadoc
1   package org.kuali.common.devops.aws.sysadmin;
2   
3   import java.io.IOException;
4   
5   import org.kuali.common.devops.aws.sysadmin.model.BootstrapContext;
6   import org.kuali.common.devops.model.User;
7   import org.kuali.common.util.Assert;
8   import org.kuali.common.util.CollectionUtils;
9   import org.kuali.common.util.FormatUtils;
10  import org.kuali.common.util.channel.api.SecureChannel;
11  import org.kuali.common.util.channel.model.ChannelContext;
12  import org.kuali.common.util.channel.model.RemoteFile;
13  import org.kuali.common.util.channel.util.ChannelUtils;
14  import org.kuali.common.util.execute.Executable;
15  
16  /**
17   * Bootstrap a freshly launched Amazon Linux AWS instance
18   * 
19   * <ul>
20   * <li>Enable root ssh</li>
21   * <li>Resize the root volume to so it uses all of the allocated space</li>
22   * <li>Update the operating system to the latest and greatest <code>yum update -y</code></li>
23   * <li>Install a list of essential packages</li>
24   * </ul>
25   */
26  public final class Bootstrap implements Executable {
27  
28  	private static final String WARNING = "WARNING: Do not delete or edit this file unless you know exactly what you are doing";
29  
30  	public Bootstrap(BootstrapContext context) {
31  		this(context, false);
32  	}
33  
34  	public Bootstrap(BootstrapContext context, boolean skip) {
35  		Assert.noNulls(context);
36  		this.context = context;
37  		this.skip = skip;
38  	}
39  
40  	private final BootstrapContext context;
41  	private final boolean skip;
42  
43  	@Override
44  	public void execute() {
45  		if (skip) {
46  			return;
47  		}
48  		bootstrap();
49  	}
50  
51  	protected void bootstrap() {
52  		enableRootSSH();
53  		SecureChannel channel = null;
54  		try {
55  			channel = getChannel(context.getRoot(), false);
56  			if (!isBootstrapped(channel)) {
57  				bootstrap(channel);
58  				markAsBootstrapped(channel);
59  				Assert.isTrue(isBootstrapped(channel), "Unable to verify that this instance has been bootstrapped");
60  			}
61  		} catch (IOException e) {
62  			throw new IllegalStateException("Unexpected IO error", e);
63  		} finally {
64  			ChannelUtils.closeQuietly(channel);
65  		}
66  	}
67  
68  	protected boolean isBootstrapped(SecureChannel channel) {
69  		RemoteFile completed = getBootStrapCompletedFile();
70  		return channel.exists(completed.getAbsolutePath());
71  	}
72  
73  	protected void bootstrap(SecureChannel channel) {
74  		// Re-size the root volume so it uses all of the allocated space
75  		String command1 = "resize2fs " + context.getRootVolumeDeviceName();
76  
77  		// Update the general operating system to the latest and greatest
78  		String command2 = "yum --assumeyes update";
79  
80  		// Invoke the commands
81  		channel.exec(command1, command2);
82  
83  		// Install custom packages (if any)
84  		if (context.getPackages().size() > 0) {
85  			String command = "yum --assumeyes install " + CollectionUtils.getSpaceSeparatedString(context.getPackages());
86  			channel.exec(command);
87  		}
88  	}
89  
90  	protected void markAsBootstrapped(SecureChannel channel) {
91  		RemoteFile completed = getBootStrapCompletedFile();
92  		String content = "bootstrapping completed: " + FormatUtils.getDate(System.currentTimeMillis()) + "\n" + WARNING;
93  		channel.scpString(content, completed);
94  	}
95  
96  	protected RemoteFile getBootStrapCompletedFile() {
97  		return new RemoteFile.Builder(context.getBootstrapCompletedAbsolutePath()).build();
98  	}
99  
100 	/**
101 	 * Connect as ec2-user to see if root ssh is enabled. If it isn't, enable it.
102 	 */
103 	protected void enableRootSSH() {
104 		SecureChannel channel = null;
105 		try {
106 			channel = getChannel(context.getSshEnabledUser(), true);
107 			boolean enabled = isRootSSHEnabled(channel);
108 			if (!enabled) {
109 				enableRootSSH(channel);
110 				markAsRootSSHEnabled(channel);
111 				Assert.isTrue(isRootSSHEnabled(channel), "Unable to verify that root ssh is enabled");
112 			}
113 		} catch (IOException e) {
114 			throw new IllegalStateException("Unexpected IO error", e);
115 		} finally {
116 			ChannelUtils.closeQuietly(channel);
117 		}
118 	}
119 
120 	protected void enableRootSSH(SecureChannel channel) {
121 		// ServiceOverride sshd = null; // context.getSshdOverride().getService();
122 
123 		String src = context.getSshdOverride().getConfigFileOverrideLocation();
124 		String dst = context.getSshEnabledUser().getHome() + "/.bootstrap/" + null; // sshd.getConfigFileName();
125 
126 		String command1 = "sudo cp " + context.getSshEnabledUser().getAuthorizedKeys() + " " + context.getRoot().getAuthorizedKeys();
127 		String command2 = "sudo cp " + dst + " " + null; // sshd.getConfigFileAbsolutePath();
128 		String command3 = "sudo service " + null; // sshd.getName() + " restart";
129 
130 		RemoteFile file = new RemoteFile.Builder(dst).build();
131 
132 		channel.exec(command1); // copy authorized_keys from ec2-user to root. This allows root to ssh
133 		channel.scp(src, file); // create an sshd_config file in the ec2-users home directory from our internally modified copy
134 		channel.exec(command2); // copy the updated sshd_config file to /etc/ssh/sshd_config
135 		channel.exec(command3); // restart the sshd service
136 
137 	}
138 
139 	protected void markAsRootSSHEnabled(SecureChannel channel) {
140 		// Leave a marker file on the file system indicating that root ssh is now enabled
141 		RemoteFile enabled = getRootSSHEnabledFile(context.getSshEnabledUser());
142 		String content = "root ssh enabled: " + FormatUtils.getDate(System.currentTimeMillis()) + "\n" + WARNING;
143 		channel.scpString(content, enabled); // Create /home/ec2-user/.bootstrap/root-ssh.enabled
144 	}
145 
146 	protected RemoteFile getRootSSHEnabledFile(User ec2User) {
147 		String absolutePath = ec2User.getHome() + "/.bootstrap/root-ssh.enabled";
148 		return new RemoteFile.Builder(absolutePath).build();
149 	}
150 
151 	/**
152 	 * Return true only if the file /home/ec2-user/.bootstrap/root-ssh.enabled exists
153 	 */
154 	protected boolean isRootSSHEnabled(SecureChannel channel) {
155 		RemoteFile enabled = getRootSSHEnabledFile(context.getSshEnabledUser());
156 		return channel.exists(enabled.getAbsolutePath());
157 	}
158 
159 	protected SecureChannel getChannel(User user, boolean requestPseudoTerminal) throws IOException {
160 		// String dnsName = context.getHostname();
161 		// String privateKey = context.getPrivateKey();
162 		ChannelContext cc = null; // new ChannelContext.Builder(user.getLogin(), dnsName).privateKey(privateKey).requestPseudoTerminal(requestPseudoTerminal).build();
163 		return context.getService().openChannel(cc);
164 	}
165 
166 	public BootstrapContext getContext() {
167 		return context;
168 	}
169 
170 	public boolean isSkip() {
171 		return skip;
172 	}
173 
174 }