| 1 | /** |
| 2 | * |
| 3 | * Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com> |
| 4 | * |
| 5 | * ==================================================================== |
| 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | * you may not use this file except in compliance with the License. |
| 8 | * You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, software |
| 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | * See the License for the specific language governing permissions and |
| 16 | * limitations under the License. |
| 17 | * ==================================================================== |
| 18 | */ |
| 19 | package org.jclouds.scriptbuilder.statements.login; |
| 20 | |
| 21 | import static com.google.common.base.Charsets.UTF_8; |
| 22 | import static com.google.common.base.Preconditions.checkNotNull; |
| 23 | |
| 24 | import java.io.File; |
| 25 | import java.io.IOException; |
| 26 | import java.util.Map; |
| 27 | |
| 28 | import javax.annotation.Nullable; |
| 29 | |
| 30 | import org.jclouds.crypto.Sha512Crypt; |
| 31 | import org.jclouds.domain.Credentials; |
| 32 | import org.jclouds.scriptbuilder.domain.OsFamily; |
| 33 | import org.jclouds.scriptbuilder.domain.Statement; |
| 34 | import org.jclouds.scriptbuilder.domain.StatementList; |
| 35 | import org.jclouds.scriptbuilder.statements.login.AdminAccess.Configuration; |
| 36 | import org.jclouds.scriptbuilder.statements.ssh.SshStatements; |
| 37 | |
| 38 | import com.google.common.base.Function; |
| 39 | import com.google.common.base.Supplier; |
| 40 | import com.google.common.base.Throwables; |
| 41 | import com.google.common.collect.ImmutableList; |
| 42 | import com.google.common.collect.ImmutableMap; |
| 43 | import com.google.common.io.Files; |
| 44 | import com.google.inject.ImplementedBy; |
| 45 | |
| 46 | /** |
| 47 | * Controls the administrative access to a node. By default, it will perform the |
| 48 | * following: |
| 49 | * |
| 50 | * <ul> |
| 51 | * <li>setup a new admin user which folks should use as opposed to the built-in |
| 52 | * vcloud account</li> |
| 53 | * <ul> |
| 54 | * <li>associate a random password to account</li> |
| 55 | * <ul> |
| 56 | * <li>securely ( use sha 512 on client side and literally rewrite the shadow |
| 57 | * entry, rather than pass password to OS in a script )</li> |
| 58 | * </ul> |
| 59 | * <li>associate the users' ssh public key with the account for login</li> <li> |
| 60 | * associate it with the os group wheel</li> </ul> <li>create os group wheel</li> |
| 61 | * <li>add sudoers for nopassword access to root by group wheel</li> <li>reset |
| 62 | * root password securely</li> <li>lockdown sshd_config for no root login, nor |
| 63 | * passwords allowed</li> </ul> |
| 64 | * |
| 65 | * @author Adrian Cole |
| 66 | */ |
| 67 | public class AdminAccess implements Statement, Function<Configuration, AdminAccess> { |
| 68 | public static AdminAccess.Builder builder() { |
| 69 | return new Builder(); |
| 70 | } |
| 71 | |
| 72 | public static AdminAccess.Builder builder(Function<String, String> cryptFunction) { |
| 73 | return new Builder(cryptFunction); |
| 74 | } |
| 75 | |
| 76 | public static AdminAccess standard() { |
| 77 | return new Builder().build(); |
| 78 | } |
| 79 | |
| 80 | @ImplementedBy(DefaultConfiguration.class) |
| 81 | public static interface Configuration { |
| 82 | Supplier<String> defaultAdminUsername(); |
| 83 | |
| 84 | Supplier<Map<String, String>> defaultAdminSshKeys(); |
| 85 | |
| 86 | Supplier<String> passwordGenerator(); |
| 87 | |
| 88 | Function<String, String> cryptFunction(); |
| 89 | } |
| 90 | |
| 91 | public static class Builder { |
| 92 | private final Function<String, String> cryptFunction; |
| 93 | |
| 94 | public Builder() { |
| 95 | this(Sha512Crypt.function()); |
| 96 | } |
| 97 | |
| 98 | public Builder(Function<String, String> cryptFunction) { |
| 99 | this.cryptFunction = cryptFunction; |
| 100 | } |
| 101 | |
| 102 | private String adminUsername; |
| 103 | private String adminPublicKey; |
| 104 | private File adminPublicKeyFile; |
| 105 | private String adminPrivateKey; |
| 106 | private File adminPrivateKeyFile; |
| 107 | private String adminPassword; |
| 108 | private String loginPassword; |
| 109 | private boolean lockSsh = true; |
| 110 | private boolean grantSudoToAdminUser = true; |
| 111 | private boolean authorizeAdminPublicKey = true; |
| 112 | private boolean installAdminPrivateKey = false; |
| 113 | private boolean resetLoginPassword = true; |
| 114 | |
| 115 | public AdminAccess.Builder adminUsername(String adminUsername) { |
| 116 | this.adminUsername = adminUsername; |
| 117 | return this; |
| 118 | } |
| 119 | |
| 120 | public AdminAccess.Builder adminPassword(String adminPassword) { |
| 121 | this.adminPassword = adminPassword; |
| 122 | return this; |
| 123 | } |
| 124 | |
| 125 | public AdminAccess.Builder loginPassword(String loginPassword) { |
| 126 | this.loginPassword = loginPassword; |
| 127 | return this; |
| 128 | } |
| 129 | |
| 130 | public AdminAccess.Builder lockSsh(boolean lockSsh) { |
| 131 | this.lockSsh = lockSsh; |
| 132 | return this; |
| 133 | } |
| 134 | |
| 135 | public AdminAccess.Builder resetLoginPassword(boolean resetLoginPassword) { |
| 136 | this.resetLoginPassword = resetLoginPassword; |
| 137 | return this; |
| 138 | } |
| 139 | |
| 140 | public AdminAccess.Builder authorizeAdminPublicKey(boolean authorizeAdminPublicKey) { |
| 141 | this.authorizeAdminPublicKey = authorizeAdminPublicKey; |
| 142 | return this; |
| 143 | } |
| 144 | |
| 145 | public AdminAccess.Builder installAdminPrivateKey(boolean installAdminPrivateKey) { |
| 146 | this.installAdminPrivateKey = installAdminPrivateKey; |
| 147 | return this; |
| 148 | } |
| 149 | |
| 150 | public AdminAccess.Builder grantSudoToAdminUser(boolean grantSudoToAdminUser) { |
| 151 | this.grantSudoToAdminUser = grantSudoToAdminUser; |
| 152 | return this; |
| 153 | } |
| 154 | |
| 155 | public AdminAccess.Builder adminPublicKey(File adminPublicKey) { |
| 156 | this.adminPublicKeyFile = adminPublicKey; |
| 157 | this.adminPublicKey = null; |
| 158 | return this; |
| 159 | } |
| 160 | |
| 161 | public AdminAccess.Builder adminPublicKey(String adminPublicKey) { |
| 162 | this.adminPublicKey = adminPublicKey; |
| 163 | this.adminPublicKeyFile = null; |
| 164 | return this; |
| 165 | } |
| 166 | |
| 167 | public AdminAccess.Builder adminPrivateKey(File adminPrivateKey) { |
| 168 | this.adminPrivateKeyFile = adminPrivateKey; |
| 169 | this.adminPrivateKey = null; |
| 170 | return this; |
| 171 | } |
| 172 | |
| 173 | public AdminAccess.Builder adminPrivateKey(String adminPrivateKey) { |
| 174 | this.adminPrivateKey = adminPrivateKey; |
| 175 | this.adminPrivateKeyFile = null; |
| 176 | return this; |
| 177 | } |
| 178 | |
| 179 | public AdminAccess build() { |
| 180 | try { |
| 181 | String adminPublicKey = this.adminPublicKey; |
| 182 | if (adminPublicKey == null && adminPublicKeyFile != null) |
| 183 | adminPublicKey = Files.toString(adminPublicKeyFile, UTF_8); |
| 184 | String adminPrivateKey = this.adminPrivateKey; |
| 185 | if (adminPrivateKey == null && adminPrivateKeyFile != null) |
| 186 | adminPrivateKey = Files.toString(adminPrivateKeyFile, UTF_8); |
| 187 | return new AdminAccess(adminUsername, adminPublicKey, adminPrivateKey, adminPassword, loginPassword, |
| 188 | lockSsh, grantSudoToAdminUser, authorizeAdminPublicKey, installAdminPrivateKey, resetLoginPassword, |
| 189 | cryptFunction); |
| 190 | } catch (IOException e) { |
| 191 | Throwables.propagate(e); |
| 192 | return null; |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | private final String adminUsername; |
| 198 | private final String adminPublicKey; |
| 199 | private final String adminPrivateKey; |
| 200 | private final String adminPassword; |
| 201 | private final String loginPassword; |
| 202 | private final boolean lockSsh; |
| 203 | private final boolean grantSudoToAdminUser; |
| 204 | private final boolean authorizeAdminPublicKey; |
| 205 | private final boolean installAdminPrivateKey; |
| 206 | private final boolean resetLoginPassword; |
| 207 | private final Function<String, String> cryptFunction; |
| 208 | private final Credentials adminCredentials; |
| 209 | |
| 210 | protected AdminAccess(@Nullable String adminUsername, @Nullable String adminPublicKey, |
| 211 | @Nullable String adminPrivateKey, @Nullable String adminPassword, @Nullable String loginPassword, |
| 212 | boolean lockSsh, boolean grantSudoToAdminUser, boolean authorizeAdminPublicKey, |
| 213 | boolean installAdminPrivateKey, boolean resetLoginPassword, Function<String, String> cryptFunction) { |
| 214 | this.adminUsername = adminUsername; |
| 215 | this.adminPublicKey = adminPublicKey; |
| 216 | this.adminPrivateKey = adminPrivateKey; |
| 217 | this.adminPassword = adminPassword; |
| 218 | this.loginPassword = loginPassword; |
| 219 | this.lockSsh = lockSsh; |
| 220 | this.grantSudoToAdminUser = grantSudoToAdminUser; |
| 221 | this.authorizeAdminPublicKey = authorizeAdminPublicKey; |
| 222 | this.installAdminPrivateKey = installAdminPrivateKey; |
| 223 | this.resetLoginPassword = resetLoginPassword; |
| 224 | this.cryptFunction = cryptFunction; |
| 225 | if (adminUsername != null && authorizeAdminPublicKey && adminPrivateKey != null) |
| 226 | this.adminCredentials = new Credentials(adminUsername, adminPrivateKey); |
| 227 | else |
| 228 | this.adminCredentials = null; |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * |
| 233 | * @return new credentials or null if unchanged or unavailable |
| 234 | */ |
| 235 | @Nullable |
| 236 | public Credentials getAdminCredentials() { |
| 237 | return adminCredentials; |
| 238 | } |
| 239 | |
| 240 | @Override |
| 241 | public Iterable<String> functionDependencies(OsFamily family) { |
| 242 | return ImmutableList.of(); |
| 243 | } |
| 244 | |
| 245 | @Override |
| 246 | public AdminAccess apply(Configuration configuration) { |
| 247 | Builder builder = AdminAccess.builder(configuration.cryptFunction()); |
| 248 | builder.adminUsername(this.adminUsername != null ? this.adminUsername : configuration.defaultAdminUsername() |
| 249 | .get()); |
| 250 | builder.adminPassword(this.adminPassword != null ? this.adminPassword : configuration.passwordGenerator().get()); |
| 251 | Map<String, String> adminSshKeys = (adminPublicKey != null && adminPrivateKey != null) ? ImmutableMap.of( |
| 252 | "public", adminPublicKey, "private", adminPrivateKey) : configuration.defaultAdminSshKeys().get(); |
| 253 | builder.adminPublicKey(adminSshKeys.get("public")); |
| 254 | builder.adminPrivateKey(adminSshKeys.get("private")); |
| 255 | builder.loginPassword(this.loginPassword != null ? this.loginPassword : configuration.passwordGenerator().get()); |
| 256 | builder.grantSudoToAdminUser(this.grantSudoToAdminUser); |
| 257 | builder.authorizeAdminPublicKey(this.authorizeAdminPublicKey); |
| 258 | builder.installAdminPrivateKey(this.installAdminPrivateKey); |
| 259 | builder.lockSsh(this.lockSsh); |
| 260 | builder.resetLoginPassword(this.resetLoginPassword); |
| 261 | return builder.build(); |
| 262 | } |
| 263 | |
| 264 | @Override |
| 265 | public String render(OsFamily family) { |
| 266 | checkNotNull(family, "family"); |
| 267 | if (family == OsFamily.WINDOWS) |
| 268 | throw new UnsupportedOperationException("windows not yet implemented"); |
| 269 | checkNotNull(adminUsername, "adminUsername"); |
| 270 | checkNotNull(adminPassword, "adminPassword"); |
| 271 | checkNotNull(adminPublicKey, "adminPublicKey"); |
| 272 | checkNotNull(adminPrivateKey, "adminPrivateKey"); |
| 273 | checkNotNull(loginPassword, "loginPassword"); |
| 274 | |
| 275 | ImmutableList.Builder<Statement> statements = ImmutableList.<Statement> builder(); |
| 276 | UserAdd.Builder userBuilder = UserAdd.builder(); |
| 277 | userBuilder.login(adminUsername); |
| 278 | if (authorizeAdminPublicKey) |
| 279 | userBuilder.authorizeRSAPublicKey(adminPublicKey); |
| 280 | userBuilder.password(adminPassword); |
| 281 | if (installAdminPrivateKey) |
| 282 | userBuilder.installRSAPrivateKey(adminPrivateKey); |
| 283 | if (grantSudoToAdminUser) { |
| 284 | statements.add(SudoStatements.createWheel()); |
| 285 | userBuilder.group("wheel"); |
| 286 | } |
| 287 | statements.add(userBuilder.build().cryptFunction(cryptFunction)); |
| 288 | if (lockSsh) |
| 289 | statements.add(SshStatements.lockSshd()); |
| 290 | if (resetLoginPassword) { |
| 291 | statements.add(ShadowStatements.resetLoginUserPasswordTo(loginPassword).cryptFunction(cryptFunction)); |
| 292 | } |
| 293 | return new StatementList(statements.build()).render(family); |
| 294 | } |
| 295 | } |